@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,65 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
/** Type-tag display labels keyed by AideFileType. */
|
|
4
|
+
const TYPE_TAG = {
|
|
5
|
+
intent: "intent",
|
|
6
|
+
research: "research",
|
|
7
|
+
plan: "plan",
|
|
8
|
+
todo: "todo",
|
|
9
|
+
};
|
|
10
|
+
/** Type-tag colors keyed by AideFileType. */
|
|
11
|
+
const TYPE_COLOR = {
|
|
12
|
+
intent: "cyan",
|
|
13
|
+
research: "yellow",
|
|
14
|
+
plan: "blue",
|
|
15
|
+
todo: "magenta",
|
|
16
|
+
};
|
|
17
|
+
/** Render one flat node row in the tree. */
|
|
18
|
+
function renderRow(flatNode, index, cursorIndex, isDeepView, expandedDirs, width) {
|
|
19
|
+
const { node, depth } = flatNode;
|
|
20
|
+
const isCursor = index === cursorIndex;
|
|
21
|
+
const indent = " ".repeat(depth);
|
|
22
|
+
if (node.kind === "dir") {
|
|
23
|
+
const label = node.path === "." ? ". /" : `${node.path}/`;
|
|
24
|
+
const expandIndicator = expandedDirs.has(node.path) ? "v " : "> ";
|
|
25
|
+
return (_jsx(Box, { children: _jsxs(Text, { bold: isCursor, color: isCursor ? "white" : "gray", children: [indent, expandIndicator, label] }) }, `dir-${node.path}-${index}`));
|
|
26
|
+
}
|
|
27
|
+
// File node — render as a single <Text> to prevent Ink from wrapping mid-element.
|
|
28
|
+
const { file } = node;
|
|
29
|
+
const filename = file.relativePath.split("/").pop() ?? file.relativePath;
|
|
30
|
+
const connector = "└── ";
|
|
31
|
+
const tag = TYPE_TAG[file.type] ?? file.type;
|
|
32
|
+
const tagColor = TYPE_COLOR[file.type] ?? "white";
|
|
33
|
+
const prefix = `${indent}${connector}${filename} `;
|
|
34
|
+
const tagStr = `[${tag}]`;
|
|
35
|
+
// Truncate summary to fit within available panel width.
|
|
36
|
+
const fixedLen = prefix.length + tagStr.length;
|
|
37
|
+
const remaining = width - fixedLen;
|
|
38
|
+
let summary = "";
|
|
39
|
+
if (isDeepView && file.summary && remaining > 10) {
|
|
40
|
+
const full = ` — ${file.summary}`;
|
|
41
|
+
summary = full.length <= remaining ? full : `${full.slice(0, remaining - 3)}…`;
|
|
42
|
+
}
|
|
43
|
+
return (_jsx(Box, { children: _jsxs(Text, { bold: isCursor, backgroundColor: isCursor ? "blue" : undefined, wrap: "truncate", children: [prefix, _jsx(Text, { color: tagColor, children: tagStr }), summary ? _jsx(Text, { color: "gray", children: summary }) : null] }) }, `file-${file.relativePath}-${index}`));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Renders the left-panel tree of .aide files with cursor highlighting and optional summaries.
|
|
47
|
+
* Receives pre-filtered visibleNodes from App — manages no state and performs no filtering.
|
|
48
|
+
* Shows expand/collapse indicators on dir nodes and context-aware footer hints.
|
|
49
|
+
*/
|
|
50
|
+
export default function TreePanel({ visibleNodes, cursorIndex, searchFilter, isDeepView, expandedDirs, cursorOnDir, cursorDirExpanded, width, }) {
|
|
51
|
+
let hintText;
|
|
52
|
+
if (cursorOnDir) {
|
|
53
|
+
const escClear = searchFilter ? " [esc] clear" : "";
|
|
54
|
+
hintText = cursorDirExpanded
|
|
55
|
+
? `${escClear} [↑↓] navigate [enter] collapse [tab] deep view`
|
|
56
|
+
: `${escClear} [↑↓] navigate [enter] expand [tab] deep view`;
|
|
57
|
+
}
|
|
58
|
+
else if (searchFilter) {
|
|
59
|
+
hintText = " [esc] clear [↑↓] navigate [enter] drill in";
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
hintText = " [↑↓] navigate [enter] drill in [tab] deep view";
|
|
63
|
+
}
|
|
64
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [visibleNodes.length === 0 ? (_jsxs(Text, { color: "gray", children: [" No results for \"", searchFilter, "\""] })) : (visibleNodes.map((fn, i) => renderRow(fn, i, cursorIndex, isDeepView, expandedDirs, width))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: hintText }) })] }));
|
|
65
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AideFile, TreeNode } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Convert a flat AideFile[] from scan() into a hierarchical TreeNode[] for TUI rendering.
|
|
4
|
+
* Directories are sorted alphabetically; files within each directory are sorted by type priority.
|
|
5
|
+
* Root-level files appear under the "." directory group.
|
|
6
|
+
*/
|
|
7
|
+
export default function buildTreeData(files: AideFile[]): TreeNode[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Priority order for sorting files within a directory: intent first, then research, plan, todo. */
|
|
2
|
+
const TYPE_PRIORITY = {
|
|
3
|
+
intent: 0,
|
|
4
|
+
research: 1,
|
|
5
|
+
plan: 2,
|
|
6
|
+
todo: 3,
|
|
7
|
+
};
|
|
8
|
+
/** Derive the display directory path for a file: POSIX-normalized dirname, or "." for root files. */
|
|
9
|
+
function dirOf(file) {
|
|
10
|
+
const parts = file.relativePath.split("/");
|
|
11
|
+
if (parts.length === 1)
|
|
12
|
+
return ".";
|
|
13
|
+
return parts.slice(0, -1).join("/");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Convert a flat AideFile[] from scan() into a hierarchical TreeNode[] for TUI rendering.
|
|
17
|
+
* Directories are sorted alphabetically; files within each directory are sorted by type priority.
|
|
18
|
+
* Root-level files appear under the "." directory group.
|
|
19
|
+
*/
|
|
20
|
+
export default function buildTreeData(files) {
|
|
21
|
+
// Group files by their directory path.
|
|
22
|
+
const byDir = new Map();
|
|
23
|
+
for (const file of files) {
|
|
24
|
+
const dir = dirOf(file);
|
|
25
|
+
const group = byDir.get(dir) ?? [];
|
|
26
|
+
group.push(file);
|
|
27
|
+
byDir.set(dir, group);
|
|
28
|
+
}
|
|
29
|
+
// Sort files within each directory by type priority, then by filename.
|
|
30
|
+
for (const group of byDir.values()) {
|
|
31
|
+
group.sort((a, b) => {
|
|
32
|
+
const pd = TYPE_PRIORITY[a.type] - TYPE_PRIORITY[b.type];
|
|
33
|
+
if (pd !== 0)
|
|
34
|
+
return pd;
|
|
35
|
+
return a.relativePath.localeCompare(b.relativePath);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// Sort directories alphabetically, root "." first.
|
|
39
|
+
const dirs = [...byDir.keys()].sort((a, b) => {
|
|
40
|
+
if (a === ".")
|
|
41
|
+
return -1;
|
|
42
|
+
if (b === ".")
|
|
43
|
+
return 1;
|
|
44
|
+
return a.localeCompare(b);
|
|
45
|
+
});
|
|
46
|
+
return dirs.map((dir) => ({
|
|
47
|
+
kind: "dir",
|
|
48
|
+
path: dir,
|
|
49
|
+
children: (byDir.get(dir) ?? []).map((file) => ({ kind: "file", file })),
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AideFile, TreeNode } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Given a dir-kind TreeNode, returns the first file child whose type is "intent"
|
|
4
|
+
* (i.e. a `.aide` or `intent.aide` file), or null if the dir has no intent child.
|
|
5
|
+
*
|
|
6
|
+
* This is a pure synchronous function — it reads from the already-loaded TreeNode
|
|
7
|
+
* data, not the filesystem. Its primary role is powering the detail panel auto-load:
|
|
8
|
+
* when the cursor lands on a dir node, App calls findPrimaryIntent to obtain the
|
|
9
|
+
* intent file so the detail panel can display its frontmatter without requiring
|
|
10
|
+
* the user to expand and explicitly select the file.
|
|
11
|
+
*/
|
|
12
|
+
export default function findPrimaryIntent(node: TreeNode): AideFile | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Given a dir-kind TreeNode, returns the first file child whose type is "intent"
|
|
3
|
+
* (i.e. a `.aide` or `intent.aide` file), or null if the dir has no intent child.
|
|
4
|
+
*
|
|
5
|
+
* This is a pure synchronous function — it reads from the already-loaded TreeNode
|
|
6
|
+
* data, not the filesystem. Its primary role is powering the detail panel auto-load:
|
|
7
|
+
* when the cursor lands on a dir node, App calls findPrimaryIntent to obtain the
|
|
8
|
+
* intent file so the detail panel can display its frontmatter without requiring
|
|
9
|
+
* the user to expand and explicitly select the file.
|
|
10
|
+
*/
|
|
11
|
+
export default function findPrimaryIntent(node) {
|
|
12
|
+
if (node.kind !== "dir")
|
|
13
|
+
return null;
|
|
14
|
+
for (const child of node.children) {
|
|
15
|
+
if (child.kind === "file" && child.file.type === "intent") {
|
|
16
|
+
return child.file;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TreeNode } from "../../types/index.js";
|
|
2
|
+
/** A flattened node with its depth for rendering and cursor indexing. */
|
|
3
|
+
export interface FlatNode {
|
|
4
|
+
node: TreeNode;
|
|
5
|
+
depth: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Convert a hierarchical TreeNode[] into a flat array of { node, depth } entries
|
|
9
|
+
* in display order (depth-first). Dir nodes always appear as cursor stops.
|
|
10
|
+
* A dir's children are only emitted when the dir's path is in `expandedDirs`.
|
|
11
|
+
*
|
|
12
|
+
* @param nodes - The tree nodes to flatten.
|
|
13
|
+
* @param expandedDirs - Set of dir paths that are currently expanded. Defaults to empty Set (all collapsed).
|
|
14
|
+
* @param depth - Current recursion depth (internal use only).
|
|
15
|
+
*/
|
|
16
|
+
export default function flattenTree(nodes: TreeNode[], expandedDirs?: Set<string>, depth?: number): FlatNode[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a hierarchical TreeNode[] into a flat array of { node, depth } entries
|
|
3
|
+
* in display order (depth-first). Dir nodes always appear as cursor stops.
|
|
4
|
+
* A dir's children are only emitted when the dir's path is in `expandedDirs`.
|
|
5
|
+
*
|
|
6
|
+
* @param nodes - The tree nodes to flatten.
|
|
7
|
+
* @param expandedDirs - Set of dir paths that are currently expanded. Defaults to empty Set (all collapsed).
|
|
8
|
+
* @param depth - Current recursion depth (internal use only).
|
|
9
|
+
*/
|
|
10
|
+
export default function flattenTree(nodes, expandedDirs = new Set(), depth = 0) {
|
|
11
|
+
const result = [];
|
|
12
|
+
for (const node of nodes) {
|
|
13
|
+
result.push({ node, depth });
|
|
14
|
+
if (node.kind === "dir" && expandedDirs.has(node.path)) {
|
|
15
|
+
for (const child of flattenTree(node.children, expandedDirs, depth + 1))
|
|
16
|
+
result.push(child);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { render } from "ink";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import scan from "../util/scan/index.js";
|
|
6
|
+
import buildTreeData from "../cli/buildTreeData/index.js";
|
|
7
|
+
import App from "../cli/App/index.js";
|
|
8
|
+
const root = process.argv[2] ?? process.cwd();
|
|
9
|
+
if (!existsSync(root)) {
|
|
10
|
+
process.stderr.write(`aide-tree: path not found: ${root}\nUsage: aide-tree [project-root]\n`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const result = await scan(root, undefined, true);
|
|
14
|
+
const initialNodes = buildTreeData(result.files);
|
|
15
|
+
render(_jsx(App, { root: root, initialNodes: initialNodes }));
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import writeMcpEntry from "./writeMcpEntry/index.js";
|
|
3
|
+
import writeInitCommand from "./writeInitCommand/index.js";
|
|
4
|
+
const MCP_LABEL = ".mcp.json";
|
|
5
|
+
const CMD_LABEL = ".claude/commands/aide/init.md";
|
|
6
|
+
export async function runInit(cwd, write = (line) => process.stdout.write(line + "\n")) {
|
|
7
|
+
const mcpResult = await writeMcpEntry(cwd);
|
|
8
|
+
const cmdResult = await writeInitCommand(cwd);
|
|
9
|
+
write(`[${mcpResult.status}] ${MCP_LABEL} — ${mcpResult.message}`);
|
|
10
|
+
write(`[${cmdResult.status}] ${CMD_LABEL} — ${cmdResult.message}`);
|
|
11
|
+
if (mcpResult.status === "exists" && cmdResult.status === "exists") {
|
|
12
|
+
write("Already set up. Run /aide:init in Claude Code to continue.");
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
write("Done. Open Claude Code and run /aide:init to complete setup.");
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
(async () => {
|
|
19
|
+
if (process.argv.includes("--help")) {
|
|
20
|
+
process.stdout.write("Usage: npx @aidemd-mcp/server init\n" +
|
|
21
|
+
"Wires the AIDE MCP server and init command into the current project.\n");
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const code = await runInit(process.cwd());
|
|
26
|
+
process.exit(code);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
})();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface WriteInitCommandResult {
|
|
2
|
+
status: "created" | "exists";
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Write the `/aide:init` slash command file to the host project.
|
|
7
|
+
*
|
|
8
|
+
* Target path is `<projectRoot>/.claude/commands/aide/init.md`. Returns
|
|
9
|
+
* `exists` when the file is already present on disk. Otherwise creates the
|
|
10
|
+
* full parent directory tree, reads the canonical content via
|
|
11
|
+
* `readCanonicalDoc`, writes the file, and returns `created`.
|
|
12
|
+
*/
|
|
13
|
+
export default function writeInitCommand(projectRoot: string): Promise<WriteInitCommandResult>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { readCanonicalDoc } from "../../../tools/init/initContent/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Write the `/aide:init` slash command file to the host project.
|
|
6
|
+
*
|
|
7
|
+
* Target path is `<projectRoot>/.claude/commands/aide/init.md`. Returns
|
|
8
|
+
* `exists` when the file is already present on disk. Otherwise creates the
|
|
9
|
+
* full parent directory tree, reads the canonical content via
|
|
10
|
+
* `readCanonicalDoc`, writes the file, and returns `created`.
|
|
11
|
+
*/
|
|
12
|
+
export default async function writeInitCommand(projectRoot) {
|
|
13
|
+
const commandPath = join(projectRoot, ".claude", "commands", "aide", "init.md");
|
|
14
|
+
try {
|
|
15
|
+
await access(commandPath);
|
|
16
|
+
return { status: "exists", message: "/aide:init command already present" };
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// ENOENT — file does not exist, proceed to create
|
|
20
|
+
}
|
|
21
|
+
await mkdir(dirname(commandPath), { recursive: true });
|
|
22
|
+
const content = readCanonicalDoc("commands/aide/init");
|
|
23
|
+
await writeFile(commandPath, content, "utf-8");
|
|
24
|
+
return { status: "created", message: "/aide:init command" };
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface WriteMcpEntryResult {
|
|
2
|
+
status: "created" | "exists";
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Read-parse-merge-write for `.mcp.json` in the given project root.
|
|
7
|
+
*
|
|
8
|
+
* Returns `exists` when the aide entry is already present (dual-key check:
|
|
9
|
+
* `aide` or `aidemd-mcp`). Returns `created` after merging and writing the
|
|
10
|
+
* new entry. Throws on malformed JSON or write failure.
|
|
11
|
+
*/
|
|
12
|
+
export default function writeMcpEntry(projectRoot: string): Promise<WriteMcpEntryResult>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { mcpEntry } from "../../../tools/init/wireMcp/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Read-parse-merge-write for `.mcp.json` in the given project root.
|
|
6
|
+
*
|
|
7
|
+
* Returns `exists` when the aide entry is already present (dual-key check:
|
|
8
|
+
* `aide` or `aidemd-mcp`). Returns `created` after merging and writing the
|
|
9
|
+
* new entry. Throws on malformed JSON or write failure.
|
|
10
|
+
*/
|
|
11
|
+
export default async function writeMcpEntry(projectRoot) {
|
|
12
|
+
const mcpPath = join(projectRoot, ".mcp.json");
|
|
13
|
+
let existing;
|
|
14
|
+
try {
|
|
15
|
+
existing = await readFile(mcpPath, "utf-8");
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
if (err.code === "ENOENT") {
|
|
19
|
+
existing = "";
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
let config = {};
|
|
26
|
+
if (existing) {
|
|
27
|
+
try {
|
|
28
|
+
config = JSON.parse(existing);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error(`.mcp.json exists but contains invalid JSON. Fix the syntax error and re-run.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const servers = (config.mcpServers ?? {});
|
|
35
|
+
if ("aide" in servers || "aidemd-mcp" in servers) {
|
|
36
|
+
return { status: "exists", message: "aide server already configured" };
|
|
37
|
+
}
|
|
38
|
+
const existingCount = Object.keys(servers).length;
|
|
39
|
+
const merged = {
|
|
40
|
+
...config,
|
|
41
|
+
mcpServers: {
|
|
42
|
+
...servers,
|
|
43
|
+
aide: mcpEntry(),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
await writeFile(mcpPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
47
|
+
const message = existingCount > 0
|
|
48
|
+
? `aide MCP server entry (merged with ${existingCount} existing server${existingCount === 1 ? "" : "s"})`
|
|
49
|
+
: "aide MCP server entry";
|
|
50
|
+
return { status: "created", message };
|
|
51
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import discover, { DiscoverInput } from "./tools/discover/index.js";
|
|
6
|
+
import read, { ReadInput } from "./tools/read/index.js";
|
|
7
|
+
import scaffold, { ScaffoldInput } from "./tools/scaffold/index.js";
|
|
8
|
+
import validate, { ValidateInput } from "./tools/validate/index.js";
|
|
9
|
+
import init, { InitInput } from "./tools/init/index.js";
|
|
10
|
+
import applySteps from "./tools/init/applySteps/index.js";
|
|
11
|
+
import upgrade, { UpgradeInput } from "./tools/upgrade/index.js";
|
|
12
|
+
import applyFiles from "./tools/upgrade/applyFiles/index.js";
|
|
13
|
+
/** Parse --root flag from CLI args, default to cwd. */
|
|
14
|
+
function parseRoot() {
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const rootIdx = args.indexOf("--root");
|
|
17
|
+
if (rootIdx !== -1 && args[rootIdx + 1])
|
|
18
|
+
return args[rootIdx + 1];
|
|
19
|
+
return process.cwd();
|
|
20
|
+
}
|
|
21
|
+
const root = parseRoot();
|
|
22
|
+
const server = new Server({ name: "@aidemd-mcp/server", version: "0.2.0" }, { capabilities: { tools: {} } });
|
|
23
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
24
|
+
tools: [
|
|
25
|
+
{
|
|
26
|
+
name: "aide_discover",
|
|
27
|
+
description: "Scan for .aide spec files in this project. Returns a tree map of where specs live, following progressive disclosure.\n\nWithout a path: returns a lightweight project-wide map — file locations and types only, no content. Use this once to understand the project's spec architecture.\n\nWith a path: the response opens with the ancestor chain — the cascading intent lineage from project root down to the target directory, with each ancestor showing its description and alignment status (aligned/misaligned when set). The ancestor chain gives you the full inherited context before you read a single spec body. After the ancestor chain comes the detailed subtree of the target directory — summaries extracted from file content and anomaly warnings. Use this to drill into the area you're working on.\n\n.aide files are progressive disclosure specs that live next to orchestrator code — they contain intent (strategy, implementation contracts, anti-patterns), research (sources, data, patterns), or QA checklists (todo). Read .aide files BEFORE reading code — they are the context layer between folder structure and implementation details.\n\nFile types (.aide, intent.aide, research.aide, plan.aide, todo.aide):\n- .aide — Intent spec (default). Strategy, contracts, anti-patterns.\n- intent.aide — Same as .aide, used only when research.aide exists in the same folder.\n- research.aide — Raw research. Sources, data points, pattern synthesis.\n- plan.aide -- Architect's implementation plan. Checkboxed steps for the implementor.\n- todo.aide — QA re-alignment document. Captures where implementation drifted from intent.\n\nNever have both .aide and intent.aide in the same folder.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
path: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "Subdirectory to drill into. When provided, the response opens with the ancestor chain — the cascading intent lineage from root to target, each ancestor showing its description and alignment status — followed by the detailed subtree with summaries and warnings. When omitted, returns a shallow project-wide map (locations and types only).",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "aide_read",
|
|
40
|
+
description: "Read an .aide spec file with full context. Returns the file content, its classified type (intent/research/plan/todo), related specs in the same directory, and links found in the content (relative paths, wikilinks, URLs). Use this after aide_discover to drill into a specific spec.",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
path: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "Path to the .aide file to read",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
required: ["path"],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "aide_scaffold",
|
|
54
|
+
description: "Create new .aide spec files with automatic naming convention enforcement. Handles the naming rules: intent specs are .aide by default, but become intent.aide when research.aide exists in the same folder. Creating a research.aide auto-renames any existing .aide to intent.aide.\n\nTypes:\n- intent — Strategy, contracts, anti-patterns\n- research — Sources, data, patterns (triggers rename of existing .aide)\n- both — Creates research.aide + intent.aide pair\n- todo — QA re-alignment document for QA agents\n- plan -- Architect's implementation plan (no naming interaction with intent/research)",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
directory: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Directory where the .aide file(s) will be created",
|
|
61
|
+
},
|
|
62
|
+
type: {
|
|
63
|
+
type: "string",
|
|
64
|
+
enum: ["intent", "research", "both", "todo", "plan"],
|
|
65
|
+
description: "Type of .aide file to create",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ["directory", "type"],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "aide_validate",
|
|
73
|
+
description: "Health check for .aide spec files in the project. Detects orphaned specs (in folders with no orchestrator), missing specs (orchestrators with 3+ helper imports but no .aide), naming conflicts (.aide + intent.aide in same folder), broken links, orphaned research (research.aide without intent spec), and missing descriptions (specs with no description field in frontmatter).",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
path: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Subdirectory to validate (defaults to entire project)",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "aide_upgrade",
|
|
86
|
+
description: "Compare the AIDE methodology artifacts in this project against the canonical versions and return structured JSON results grouped by category. Use this when the user asks to update AIDE, sync AIDE, refresh AIDE, check for AIDE updates, or bring AIDE up to date. This is NOT for editing user .aide specs — it inspects methodology infrastructure only.\n\nThe tool uses a two-call pattern for progressive disclosure:\n\n**First call (no `category` param):** Returns a lightweight summary — every category with file names, statuses, and counts, but NO file content. Use this to understand what has drifted and present a summary to the user. Ask which categories they want to apply.\n\n**Second call (with `category` param):** The tool writes all differs/missing files directly to disk itself and returns a manifest — file results with `filePath`, `status` (`\"updated\"`, `\"created\"`, or `\"matches\"`), and `name`, but NO `canonicalContent`. The agent never sees file content and never uses the Write tool for methodology files.\n\nRepeat the second call for each category the user confirms.\n\nAs the calling agent, you must:\n1. Call without `category` first to get the summary\n2. Present each drifted category (differs/missing) and ask the user which to apply\n3. For each confirmed category, call again with `category=X` — the tool writes the files and returns a manifest. Report what was updated/created to the user.\n4. For the `mcp` category, the manifest still includes `prescription` data — merge the entry into the existing MCP config yourself (read → merge → write). If `malformed`, tell the user — do not overwrite.\n5. For `ide`, the manifest may include `instructions` for VS Code extension install — execute that command for the user. Zed config is written directly by the tool.\n\n**IMPORTANT — one-at-a-time wizard pattern using AskUserQuestion:**\nDo NOT present all categories at once. Walk the user through ONE category at a time using AskUserQuestion with Yes/Skip options. Stop after each question and wait for confirmation before calling with that category.\n\nCategories: pointer-stub, methodology-docs, version-metadata, commands, agents, skills, mcp, ide.\n\nUpgrade surface (user code and user .aide specs are never touched):\n- AIDE pointer stub in the agent config file\n- Canonical methodology docs under .aide/docs/\n- versions.json metadata under .aide/docs/\n- Slash commands for all pipeline phases\n- Pipeline agent files, skill templates\n- MCP server entry in the project's MCP config\n- IDE file association config (Zed settings, VS Code extension)\n\nSupports Claude Code, Cursor, Windsurf, and Copilot. Auto-detects the framework or accepts an override.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
framework: {
|
|
91
|
+
type: "string",
|
|
92
|
+
enum: ["claude", "cursor", "windsurf", "copilot"],
|
|
93
|
+
description: "Force a specific framework instead of auto-detecting. Auto-detection checks for framework-specific files/directories and defaults to Claude Code.",
|
|
94
|
+
},
|
|
95
|
+
path: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Custom project root path (defaults to server working directory)",
|
|
98
|
+
},
|
|
99
|
+
category: {
|
|
100
|
+
type: "string",
|
|
101
|
+
enum: ["pointer-stub", "methodology-docs", "version-metadata", "commands", "agents", "skills", "mcp", "ide"],
|
|
102
|
+
description: "Write all differs/missing files for this category to disk and return a manifest. Omit on the first call to get a metadata-only summary of all categories.",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "aide_init",
|
|
109
|
+
description: "Bootstrap the AIDE development environment into a project. Returns structured JSON for agent consumption — not prose.\n\nThe tool uses a two-call pattern for progressive disclosure:\n\n**First call (no `category` param):** Returns a lightweight summary — every step with `name`, `status` (would-create/would-skip/exists), `category`, and `filePath`, but NO `content` fields. Also returns `brainHints` (vault candidates) and detected `framework`. Use this to understand what needs to be done.\n\n**Second call (with `category` param):** The tool writes all `would-create` files directly to disk itself and returns a manifest — steps with `filePath`, `status` (`created` or `exists`), and `name`, but NO `content`. The agent never sees file content and never uses the Write tool for new files.\n\n**Exception — MCP steps:** For MCP steps, the manifest includes `prescription` data (key name and entry object) so the agent can read the existing config, merge, and write. The tool never touches MCP config directly.\n\n**Exception — brain category:** When calling with `category=brain`, also pass `brainPath` with the user-confirmed vault path. The tool creates the vault scaffold directories directly.\n\n**Exception — IDE VS Code steps:** IDE steps that need external tooling (VS Code CLI) return instructions for the agent to execute, since those aren't simple file writes.\n\n**IMPORTANT — one-at-a-time wizard pattern using AskUserQuestion:**\nDo NOT present a summary table of all categories. Do NOT offer \"all\" as an option. Do NOT ask conversational questions — use the `AskUserQuestion` tool with structured options at every pause point. Walk the user through ONE category at a time:\n\n1. Call without `category` first to get the metadata\n2. Present ONLY the detected framework — use AskUserQuestion with Yes/{alternatives} options. STOP.\n3. Present ONLY the first category with would-create steps — use AskUserQuestion with Yes/Skip options. STOP.\n4. If confirmed, call again with `category=X` (and `brainPath` when category is brain). The tool writes files and returns a manifest. Report what was created, then present the NEXT category with AskUserQuestion. STOP.\n5. Repeat step 4 for each remaining category in order: methodology, commands, agents, skills, mcp, brain, ide\n6. For brain: use AskUserQuestion with brainHints as labeled options (user can pick Other for custom path). STOP.\n7. For MCP: use AskUserQuestion with Merge/Skip options. Merge the `prescription` entry into the existing config yourself (read → merge → write). STOP.\n8. For IDE: use AskUserQuestion with multiSelect for Zed/VS Code/Neither. STOP.\n\nEach step is ONE AskUserQuestion → wait for selection → then proceed. Never show multiple categories at once. Never ask open-ended conversational questions.\n\nDo NOT auto-apply steps without user confirmation.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
framework: {
|
|
114
|
+
type: "string",
|
|
115
|
+
enum: ["claude", "cursor", "windsurf", "copilot"],
|
|
116
|
+
description: "Force a specific framework instead of auto-detecting. Use this when re-calling after the user confirms or overrides detection.",
|
|
117
|
+
},
|
|
118
|
+
path: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "Custom project root path (defaults to server working directory)",
|
|
121
|
+
},
|
|
122
|
+
category: {
|
|
123
|
+
type: "string",
|
|
124
|
+
enum: ["framework", "methodology", "commands", "agents", "skills", "mcp", "brain", "ide"],
|
|
125
|
+
description: "Write all would-create files for this category to disk and return a manifest. Omit on the first call to get a metadata-only summary of all steps.",
|
|
126
|
+
},
|
|
127
|
+
brainPath: {
|
|
128
|
+
type: "string",
|
|
129
|
+
description: "Resolved brain vault path. Required when category=brain. The agent provides this after interviewing the user.",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
}));
|
|
136
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
137
|
+
const { name, arguments: args } = request.params;
|
|
138
|
+
switch (name) {
|
|
139
|
+
case "aide_discover": {
|
|
140
|
+
const parsed = DiscoverInput.parse(args);
|
|
141
|
+
const result = await discover(root, parsed.path);
|
|
142
|
+
return { content: [{ type: "text", text: result }] };
|
|
143
|
+
}
|
|
144
|
+
case "aide_read": {
|
|
145
|
+
const parsed = ReadInput.parse(args);
|
|
146
|
+
const result = await read(root, parsed.path);
|
|
147
|
+
return {
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: "text",
|
|
151
|
+
text: `# ${result.type} spec\n\n${result.content}\n\n---\nSiblings: ${result.siblings.map((s) => s.relativePath).join(", ") || "none"}\nLinks: ${result.links.join(", ") || "none"}`,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
case "aide_scaffold": {
|
|
157
|
+
const parsed = ScaffoldInput.parse(args);
|
|
158
|
+
const result = await scaffold(root, parsed.directory, parsed.type);
|
|
159
|
+
return { content: [{ type: "text", text: result }] };
|
|
160
|
+
}
|
|
161
|
+
case "aide_validate": {
|
|
162
|
+
const parsed = ValidateInput.parse(args);
|
|
163
|
+
const result = await validate(root, parsed.path);
|
|
164
|
+
const summary = result.warnings.length === 0
|
|
165
|
+
? "No issues found."
|
|
166
|
+
: result.warnings.map((w) => `[${w.kind}] ${w.path} — ${w.message}`).join("\n");
|
|
167
|
+
return { content: [{ type: "text", text: summary }] };
|
|
168
|
+
}
|
|
169
|
+
case "aide_init": {
|
|
170
|
+
const parsed = InitInput.parse(args);
|
|
171
|
+
const result = await init(root, parsed.framework, parsed.path, parsed.brainPath);
|
|
172
|
+
if (parsed.category) {
|
|
173
|
+
// Category-specific call: filter to matching steps, write files to
|
|
174
|
+
// disk via applySteps, and return a manifest (no content).
|
|
175
|
+
result.steps = result.steps.filter((s) => s.category === parsed.category);
|
|
176
|
+
result.steps = await applySteps(result.steps);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Summary call: strip content to keep response small
|
|
180
|
+
result.steps = result.steps.map(({ content: _content, ...rest }) => rest);
|
|
181
|
+
}
|
|
182
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
183
|
+
}
|
|
184
|
+
case "aide_upgrade": {
|
|
185
|
+
const parsed = UpgradeInput.parse(args);
|
|
186
|
+
const result = await upgrade(root, parsed.framework, parsed.path);
|
|
187
|
+
if (parsed.category) {
|
|
188
|
+
// Category-specific call: filter to the requested category, write files
|
|
189
|
+
// to disk via applyFiles, and return a manifest (no canonicalContent).
|
|
190
|
+
const filtered = result.categories.filter((c) => c.category === parsed.category);
|
|
191
|
+
result.categories = await Promise.all(filtered.map(async (cat) => {
|
|
192
|
+
const appliedFiles = await applyFiles(cat.files);
|
|
193
|
+
// Recompute summary from post-apply statuses
|
|
194
|
+
const summary = {
|
|
195
|
+
total: appliedFiles.length,
|
|
196
|
+
differs: appliedFiles.filter((f) => f.status === "differs").length,
|
|
197
|
+
missing: appliedFiles.filter((f) => f.status === "missing").length,
|
|
198
|
+
matches: appliedFiles.filter((f) => f.status === "matches").length,
|
|
199
|
+
updated: appliedFiles.filter((f) => f.status === "updated").length,
|
|
200
|
+
created: appliedFiles.filter((f) => f.status === "created").length,
|
|
201
|
+
unchanged: appliedFiles.filter((f) => f.status === "unchanged").length,
|
|
202
|
+
};
|
|
203
|
+
// Defense in depth: strip any residual canonicalContent
|
|
204
|
+
const manifestFiles = appliedFiles.map(({ canonicalContent: _content, ...rest }) => rest);
|
|
205
|
+
return { ...cat, files: manifestFiles, summary };
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Summary call: strip canonicalContent to keep response small
|
|
210
|
+
result.categories = result.categories.map((cat) => ({
|
|
211
|
+
...cat,
|
|
212
|
+
files: cat.files.map(({ canonicalContent: _content, ...rest }) => rest),
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
216
|
+
}
|
|
217
|
+
default:
|
|
218
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
async function main() {
|
|
222
|
+
const transport = new StdioServerTransport();
|
|
223
|
+
await server.connect(transport);
|
|
224
|
+
}
|
|
225
|
+
main().catch((error) => {
|
|
226
|
+
console.error("Fatal:", error);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk from `targetPath` up to `root`, collecting .aide specs at each directory
|
|
3
|
+
* level along the way. Renders them top-down (root first, target-parent last)
|
|
4
|
+
* so agents read broadest context first before drilling into the subtree.
|
|
5
|
+
*
|
|
6
|
+
* The target directory's own specs are excluded — those appear in the subtree
|
|
7
|
+
* section rendered by buildTree. This function is concerned only with the
|
|
8
|
+
* inherited lineage above the target.
|
|
9
|
+
*
|
|
10
|
+
* Root-level spec detection uses the canonical `.aide/intent.aide` path
|
|
11
|
+
* (per aide-spec.md placement rules). At all other directory levels, checks
|
|
12
|
+
* `.aide` first, then falls back to `intent.aide`.
|
|
13
|
+
*
|
|
14
|
+
* Returns `"Ancestor chain:\n" + lines` when at least one ancestor spec is
|
|
15
|
+
* found, or an empty string if the target is the root (no ancestors above it).
|
|
16
|
+
*/
|
|
17
|
+
export default function buildAncestorChain(root: string, targetPath: string): Promise<string>;
|