@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,91 @@
1
+ # /aide:upgrade — Interactive Methodology Upgrade
2
+
3
+ > **Agent:** You are the orchestrator for this command. Do NOT delegate to a subagent.
4
+
5
+ > **CRITICAL — read this before doing anything:**
6
+ > This is a step-by-step wizard. You show ONE category at a time, ask ONE question using the `AskUserQuestion` tool with structured options, then STOP and wait.
7
+ >
8
+ > **Rules:**
9
+ > - Never show all drifted categories at once and ask "which do you want?"
10
+ > - Never offer "all" as an option
11
+ > - Every pause point MUST use `AskUserQuestion` with defined options
12
+ > - After every `AskUserQuestion`, STOP. Do not continue until the user responds.
13
+ > - The tool writes files to disk itself — you do NOT use the Write tool for file categories
14
+
15
+ Bring this project's AIDE methodology artifacts up to date with canonical by calling `aide_upgrade` and walking the user through each drifted category interactively.
16
+
17
+ ## Two-call pattern
18
+
19
+ The tool uses a **two-call pattern**. The first call (no `category`) returns a lightweight summary. The second call (with `category`) writes files to disk and returns a manifest — no file content.
20
+
21
+ ## Wizard flow
22
+
23
+ ---
24
+
25
+ ### Step 1: Call `aide_upgrade` (summary)
26
+
27
+ Call `aide_upgrade` with no arguments. The response is JSON with `framework` and `categories`. Each category has `files` (metadata only — no `canonicalContent`) and a `summary` with counts.
28
+
29
+ If all categories have `differs: 0` and `missing: 0`, tell the user everything is current and stop.
30
+
31
+ Otherwise, proceed to walk through drifted categories one at a time.
32
+
33
+ ---
34
+
35
+ ### Step 2–N: Walk through drifted categories one at a time
36
+
37
+ For each category where `differs > 0` or `missing > 0`, present ONLY that category:
38
+
39
+ - Name the category
40
+ - List which files differ or are missing (use `~` for differs, `+` for missing)
41
+ - Call `AskUserQuestion`:
42
+
43
+ ```
44
+ question: "{Category} — {count} files need updating. Apply?"
45
+ header: "{Category}"
46
+ options:
47
+ - label: "Yes, update" / description: "Write the canonical versions to disk"
48
+ - label: "Skip" / description: "Keep current versions"
49
+ ```
50
+
51
+ STOP. Wait for user response.
52
+
53
+ **If the user selects "Yes":**
54
+ 1. Call `aide_upgrade` with `category` set to that category name
55
+ 2. The tool writes all differs/missing files to disk itself and returns a manifest
56
+ 3. Report what was updated (e.g., "Updated 2 files in .aide/docs/")
57
+
58
+ Then move to the NEXT drifted category. Present it the same way with `AskUserQuestion`.
59
+
60
+ **If the user selects "Skip":** Move to the next drifted category.
61
+
62
+ **Categories that are all `matches`:** Skip silently — don't ask about them.
63
+
64
+ ---
65
+
66
+ ### Special category handling
67
+
68
+ **pointer-stub:** The tool splices the canonical stub within marker boundaries, preserving user content outside the markers. This is handled by the tool during apply — just report the result.
69
+
70
+ **mcp:** The manifest includes `prescription` data. Read the existing MCP config, show what would change, and call `AskUserQuestion`:
71
+
72
+ ```
73
+ question: "MCP config — the aide server entry needs updating. Merge?"
74
+ header: "MCP"
75
+ options:
76
+ - label: "Yes, merge" / description: "Update the aide entry in .mcp.json"
77
+ - label: "Skip" / description: "Keep current MCP config"
78
+ ```
79
+
80
+ On confirmation, read the config, merge the prescription, write with the Write tool. If `malformed`, tell the user and ask how to proceed.
81
+
82
+ **ide:** Use `AskUserQuestion` with multiSelect. VS Code steps return an `instructions` field — execute it if confirmed. Zed is written by the tool directly.
83
+
84
+ ---
85
+
86
+ ### Final step: Summary
87
+
88
+ Report what was done:
89
+ - Files updated per category
90
+ - Categories that were already current
91
+ - Categories the user skipped
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TetsuKodai Group LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @aidemd-mcp/server
2
+
3
+ MCP server that teaches any agent the AIDE (Autonomous Intel-Driven Engineering) methodology. When an agent connects, the tool descriptions themselves teach the convention — no config or documentation injection needed.
4
+
5
+ ## What is AIDE?
6
+
7
+ AIDE specs are `.aide` files that live next to orchestrator code as progressive disclosure specs. They capture the domain context that code alone doesn't — strategy, research, implementation contracts, and anti-patterns.
8
+
9
+ | File | Purpose |
10
+ |------|---------|
11
+ | `.aide` | Intent spec (default). Strategy, contracts, anti-patterns. |
12
+ | `intent.aide` | Same as `.aide` — used only when `research.aide` exists in the same folder. |
13
+ | `research.aide` | Raw research. Sources, data points, pattern synthesis. |
14
+ | `todo.aide` | QA checklist. Issues found by audit agents. |
15
+
16
+ ## Installation
17
+
18
+ Add to your MCP client config:
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "aide": {
24
+ "command": "npx",
25
+ "args": ["@aidemd-mcp/server"]
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ Or with a custom project root:
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "aide": {
37
+ "command": "npx",
38
+ "args": ["@aidemd-mcp/server", "--root", "/path/to/project"]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## Tools
45
+
46
+ ### `aide_discover`
47
+
48
+ Scans the project for all `.aide` files and returns a progressive disclosure tree map. This is the flagship tool — it teaches the agent the entire module architecture at a glance.
49
+
50
+ **Input:** optional `path` (subdirectory to scan)
51
+
52
+ **Output:** Tree showing each spec's type, location, and summary.
53
+
54
+ ### `aide_read`
55
+
56
+ Reads an `.aide` file with context awareness. Returns the file content, classified type, sibling specs in the same directory, and links found in the content (wikilinks, relative paths, URLs).
57
+
58
+ **Input:** `path` (required)
59
+
60
+ ### `aide_scaffold`
61
+
62
+ Creates new `.aide` files with automatic naming convention enforcement. Handles auto-rename logic — creating a `research.aide` will rename an existing `.aide` to `intent.aide`.
63
+
64
+ **Input:** `directory` (required), `type` (required: `intent` | `research` | `both` | `todo`)
65
+
66
+ ### `aide_validate`
67
+
68
+ Health check for `.aide` spec files. Detects orphaned specs, missing specs, naming conflicts, broken links, and orphaned research files.
69
+
70
+ **Input:** optional `path` (subdirectory to validate)
71
+
72
+ ### `aide_init`
73
+
74
+ Bootstrap the AIDE development environment into a project with one command. Detects the agent framework, writes the AIDE methodology into the agent's config file, scaffolds slash commands for every pipeline phase (`/aide-research`, `/aide-spec`, `/aide-synthesize`, `/aide-plan`, `/aide-build`, `/aide-qa`, `/aide-fix`, `/aide-refactor`), and wires this MCP server into the project's MCP config.
75
+
76
+ Supports Claude Code, Cursor, Windsurf, and Copilot. Auto-detects from marker files or accepts an override.
77
+
78
+ **Input:** optional `framework` (`claude` | `cursor` | `windsurf` | `copilot`), optional `path` (project root)
79
+
80
+ Each step is idempotent — running on an already-initialized project reports what's present without overwriting.
81
+
82
+ ## Development
83
+
84
+ ```bash
85
+ npm install
86
+ npm run build
87
+ npm test
88
+ ```
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import type { TreeNode } from "../../types/index.js";
3
+ interface AppProps {
4
+ /** Project root to scan. */
5
+ root: string;
6
+ /** Initial shallow scan results already loaded before render. */
7
+ initialNodes: TreeNode[];
8
+ }
9
+ /**
10
+ * Top-level TUI orchestrator. Owns view state (tree vs drill-in) and delegates
11
+ * to TreePanel and DetailPanel. Handles all keyboard input.
12
+ */
13
+ export default function App({ root, initialNodes }: AppProps): React.ReactElement;
14
+ export {};
@@ -0,0 +1,282 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback, useEffect, useMemo } from "react";
3
+ import { spawnSync } from "node:child_process";
4
+ import { Box, Text, useInput, useApp, useStdout } from "ink";
5
+ import { readFile } from "node:fs/promises";
6
+ import flattenTree from "../../cli/flattenTree/index.js";
7
+ import buildTreeData from "../../cli/buildTreeData/index.js";
8
+ import TreePanel from "../../cli/TreePanel/index.js";
9
+ import DetailPanel from "../../cli/DetailPanel/index.js";
10
+ import parseFrontmatter from "../../util/parseFrontmatter/index.js";
11
+ import parseBody from "../../util/parseBody/index.js";
12
+ import scan from "../../util/scan/index.js";
13
+ import findPrimaryIntent from "../../cli/findPrimaryIntent/index.js";
14
+ /**
15
+ * Top-level TUI orchestrator. Owns view state (tree vs drill-in) and delegates
16
+ * to TreePanel and DetailPanel. Handles all keyboard input.
17
+ */
18
+ export default function App({ root, initialNodes }) {
19
+ const { exit } = useApp();
20
+ const { stdout } = useStdout();
21
+ const columns = stdout?.columns ?? 80;
22
+ // --- Tree state ---
23
+ // Cache shallow results so toggling back from deep view restores them instantly.
24
+ const [shallowNodes] = useState(initialNodes);
25
+ const [treeNodes, setTreeNodes] = useState(initialNodes);
26
+ const [expandedDirs, setExpandedDirs] = useState(new Set());
27
+ const [cursorIndex, setCursorIndex] = useState(0);
28
+ const [searchFilter, setSearchFilter] = useState("");
29
+ const [isDeepView, setIsDeepView] = useState(false);
30
+ const [deepLoading, setDeepLoading] = useState(false);
31
+ // Derive flatNodes from treeNodes + expandedDirs — always in sync within the same render.
32
+ const flatNodes = useMemo(() => flattenTree(treeNodes, expandedDirs), [treeNodes, expandedDirs]);
33
+ // --- View mode ---
34
+ const [mode, setMode] = useState("tree");
35
+ // --- Drill-in state ---
36
+ const [drilledFile, setDrilledFile] = useState(null);
37
+ const [drillData, setDrillData] = useState(null);
38
+ const [drillCache] = useState(new Map());
39
+ // Single expanded section in drill-in mode; Tab cycles through sections.
40
+ const [expandedSection, setExpandedSection] = useState(null);
41
+ // --- Detail panel frontmatter (for currently selected file) ---
42
+ const [selectedFrontmatter, setSelectedFrontmatter] = useState(null);
43
+ const [fmCache] = useState(new Map());
44
+ /** Returns true if any file descendant of node matches the search filter. */
45
+ function hasMatchingDescendant(node, filter) {
46
+ if (node.kind === "file") {
47
+ const lower = filter.toLowerCase();
48
+ return (node.file.relativePath.toLowerCase().includes(lower) ||
49
+ (node.file.summary ?? "").toLowerCase().includes(lower));
50
+ }
51
+ return node.children.some((child) => hasMatchingDescendant(child, filter));
52
+ }
53
+ // Compute visible flat nodes from a single source of truth in App.
54
+ // Dir nodes are included when at least one of their file descendants matches.
55
+ // File nodes inside expanded dirs are included when they individually match.
56
+ const visibleNodes = searchFilter
57
+ ? flatNodes.filter((fn) => {
58
+ if (fn.node.kind === "dir") {
59
+ return hasMatchingDescendant(fn.node, searchFilter);
60
+ }
61
+ return (fn.node.file.relativePath.toLowerCase().includes(searchFilter.toLowerCase()) ||
62
+ (fn.node.file.summary ?? "").toLowerCase().includes(searchFilter.toLowerCase()));
63
+ })
64
+ : flatNodes;
65
+ // Clamp cursor within visible range.
66
+ const clampedCursor = Math.min(cursorIndex, Math.max(0, visibleNodes.length - 1));
67
+ // Current cursor node.
68
+ const cursorNode = visibleNodes[clampedCursor];
69
+ // Derive selectedFile: for dir nodes auto-load primary intent; for file nodes use directly.
70
+ const selectedFile = cursorNode
71
+ ? cursorNode.node.kind === "file"
72
+ ? cursorNode.node.file
73
+ : findPrimaryIntent(cursorNode.node)
74
+ : null;
75
+ // Computed booleans for the cursor's current position.
76
+ const cursorOnDir = cursorNode?.node.kind === "dir";
77
+ const cursorDirExpanded = cursorOnDir && cursorNode.node.kind === "dir" && expandedDirs.has(cursorNode.node.path);
78
+ // Load frontmatter for the detail panel whenever selectedFile changes.
79
+ useEffect(() => {
80
+ if (!selectedFile) {
81
+ setSelectedFrontmatter(null);
82
+ return;
83
+ }
84
+ if (fmCache.has(selectedFile.path)) {
85
+ setSelectedFrontmatter(fmCache.get(selectedFile.path) ?? null);
86
+ return;
87
+ }
88
+ readFile(selectedFile.path, "utf-8")
89
+ .then((content) => {
90
+ const { frontmatter } = parseFrontmatter(content);
91
+ fmCache.set(selectedFile.path, frontmatter);
92
+ setSelectedFrontmatter(frontmatter);
93
+ })
94
+ .catch(() => {
95
+ fmCache.set(selectedFile.path, null);
96
+ setSelectedFrontmatter(null);
97
+ });
98
+ }, [selectedFile?.path]);
99
+ /** Load and parse a file for drill-in view. */
100
+ const drillIntoFile = useCallback(async (file) => {
101
+ setDrilledFile(file);
102
+ setExpandedSection(null);
103
+ setMode("drill-in");
104
+ if (drillCache.has(file.path)) {
105
+ setDrillData(drillCache.get(file.path));
106
+ return;
107
+ }
108
+ try {
109
+ const content = await readFile(file.path, "utf-8");
110
+ const { frontmatter, body } = parseFrontmatter(content);
111
+ const sections = parseBody(body);
112
+ const data = { frontmatter, sections };
113
+ drillCache.set(file.path, data);
114
+ setDrillData(data);
115
+ }
116
+ catch {
117
+ setDrillData({ frontmatter: null, sections: [] });
118
+ }
119
+ }, [drillCache]);
120
+ /** Toggle deep view on/off, caching shallow results for instant restore. */
121
+ const toggleDeepView = useCallback(async () => {
122
+ if (isDeepView) {
123
+ // Restore cached shallow results — no re-scan needed.
124
+ setTreeNodes(shallowNodes);
125
+ setIsDeepView(false);
126
+ return;
127
+ }
128
+ setDeepLoading(true);
129
+ try {
130
+ const result = await scan(root, undefined, false);
131
+ const nodes = buildTreeData(result.files);
132
+ setTreeNodes(nodes);
133
+ }
134
+ catch {
135
+ // Silently keep current state on scan failure.
136
+ }
137
+ finally {
138
+ setDeepLoading(false);
139
+ setIsDeepView(true);
140
+ }
141
+ }, [isDeepView, root, shallowNodes]);
142
+ // --- Keyboard handling ---
143
+ useInput((input, key) => {
144
+ if (mode === "tree") {
145
+ if (key.upArrow) {
146
+ setCursorIndex((c) => Math.max(0, c - 1));
147
+ return;
148
+ }
149
+ if (key.downArrow) {
150
+ setCursorIndex((c) => Math.min(visibleNodes.length - 1, c + 1));
151
+ return;
152
+ }
153
+ if (key.return) {
154
+ if (!cursorNode)
155
+ return;
156
+ if (cursorNode.node.kind === "dir") {
157
+ const dirPath = cursorNode.node.path;
158
+ const isExpanded = expandedDirs.has(dirPath);
159
+ setExpandedDirs((prev) => {
160
+ const next = new Set(prev);
161
+ if (isExpanded) {
162
+ next.delete(dirPath);
163
+ }
164
+ else {
165
+ next.add(dirPath);
166
+ }
167
+ return next;
168
+ });
169
+ // When expanding, advance cursor to first child.
170
+ if (!isExpanded) {
171
+ setCursorIndex((c) => c + 1);
172
+ }
173
+ // When collapsing, cursor stays on the dir node (clampedCursor handles bounds).
174
+ }
175
+ else {
176
+ // File node: drill in.
177
+ drillIntoFile(cursorNode.node.file);
178
+ }
179
+ return;
180
+ }
181
+ if (key.tab) {
182
+ toggleDeepView();
183
+ return;
184
+ }
185
+ if (key.escape) {
186
+ // Priority 1: if cursor is on a file node inside an expanded dir, collapse parent.
187
+ if (cursorNode && cursorNode.node.kind === "file") {
188
+ // Walk backward through visibleNodes to find the nearest preceding dir at shallower depth.
189
+ const cursorDepth = cursorNode.depth;
190
+ for (let i = clampedCursor - 1; i >= 0; i--) {
191
+ const candidate = visibleNodes[i];
192
+ if (candidate.node.kind === "dir" && candidate.depth < cursorDepth) {
193
+ // Collapse this parent dir.
194
+ const parentPath = candidate.node.path;
195
+ setExpandedDirs((prev) => {
196
+ const next = new Set(prev);
197
+ next.delete(parentPath);
198
+ return next;
199
+ });
200
+ setCursorIndex(i);
201
+ return;
202
+ }
203
+ }
204
+ }
205
+ // Priority 2: if search filter non-empty, clear it.
206
+ if (searchFilter) {
207
+ setSearchFilter("");
208
+ setCursorIndex(0);
209
+ return;
210
+ }
211
+ // Priority 3: exit.
212
+ exit();
213
+ return;
214
+ }
215
+ if (key.backspace || key.delete) {
216
+ setSearchFilter((f) => f.slice(0, -1));
217
+ return;
218
+ }
219
+ // Printable characters: append to search filter.
220
+ if (input && !key.ctrl && !key.meta && input.length === 1) {
221
+ setSearchFilter((f) => f + input);
222
+ setCursorIndex(0);
223
+ }
224
+ return;
225
+ }
226
+ // Drill-in mode.
227
+ if (key.escape || key.backspace) {
228
+ setMode("tree");
229
+ setDrilledFile(null);
230
+ setDrillData(null);
231
+ return;
232
+ }
233
+ if (key.upArrow) {
234
+ const next = Math.max(0, clampedCursor - 1);
235
+ setCursorIndex(next);
236
+ const nextNode = visibleNodes[next];
237
+ const nextFile = nextNode
238
+ ? nextNode.node.kind === "file"
239
+ ? nextNode.node.file
240
+ : findPrimaryIntent(nextNode.node)
241
+ : null;
242
+ if (nextFile)
243
+ drillIntoFile(nextFile);
244
+ return;
245
+ }
246
+ if (key.downArrow) {
247
+ const next = Math.min(visibleNodes.length - 1, clampedCursor + 1);
248
+ setCursorIndex(next);
249
+ const nextNode = visibleNodes[next];
250
+ const nextFile = nextNode
251
+ ? nextNode.node.kind === "file"
252
+ ? nextNode.node.file
253
+ : findPrimaryIntent(nextNode.node)
254
+ : null;
255
+ if (nextFile)
256
+ drillIntoFile(nextFile);
257
+ return;
258
+ }
259
+ if (key.tab) {
260
+ const sectionCount = (drillData?.sections ?? []).filter((s) => s.heading !== "").length;
261
+ if (sectionCount === 0)
262
+ return;
263
+ setExpandedSection((prev) => {
264
+ if (prev === null)
265
+ return 0;
266
+ if (prev >= sectionCount - 1)
267
+ return null;
268
+ return prev + 1;
269
+ });
270
+ return;
271
+ }
272
+ if (input === "e" && drilledFile) {
273
+ const editor = process.env.VISUAL ?? process.env.EDITOR ?? "code";
274
+ spawnSync(editor, [drilledFile.path], { stdio: "inherit" });
275
+ return;
276
+ }
277
+ // Enter is a no-op in drill-in mode.
278
+ });
279
+ // --- Layout ---
280
+ const treeWidth = Math.floor(columns * 0.38);
281
+ return (_jsxs(Box, { flexDirection: "row", width: columns, children: [_jsxs(Box, { flexDirection: "column", width: treeWidth, borderStyle: "single", borderColor: "white", children: [_jsx(Text, { bold: true, children: " Intent Tree" }), deepLoading && _jsx(Text, { color: "yellow", children: " Loading summaries..." }), _jsx(TreePanel, { visibleNodes: visibleNodes, cursorIndex: clampedCursor, searchFilter: searchFilter, isDeepView: isDeepView, expandedDirs: expandedDirs, cursorOnDir: cursorOnDir, cursorDirExpanded: cursorDirExpanded, width: treeWidth - 2 })] }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, borderStyle: "single", borderColor: "white", children: [_jsx(Text, { bold: true, children: " Detail" }), _jsx(DetailPanel, { file: selectedFile, frontmatter: mode === "drill-in" ? (drillData?.frontmatter ?? null) : selectedFrontmatter, mode: mode === "drill-in" ? "drill-in" : "preview", sections: drillData?.sections ?? [], expandedSection: expandedSection, drilledFilePath: drilledFile?.relativePath ?? null })] })] }));
282
+ }
@@ -0,0 +1,24 @@
1
+ import React from "react";
2
+ import type { AideFile, AideFrontmatter, BodySection } from "../../types/index.js";
3
+ interface DetailPanelProps {
4
+ file: AideFile | null;
5
+ frontmatter: AideFrontmatter | null;
6
+ /** Controls which rendering mode is active in the right panel. */
7
+ mode: "preview" | "drill-in";
8
+ /** Body sections from the drilled-in file. */
9
+ sections: BodySection[];
10
+ /** Index of the currently expanded section, or null when all are collapsed. */
11
+ expandedSection: number | null;
12
+ /** File path shown as the panel title during drill-in, or null in preview mode. */
13
+ drilledFilePath: string | null;
14
+ }
15
+ /**
16
+ * Right-panel component that handles both preview mode (tree navigation) and
17
+ * drill-in mode (formatted frontmatter card with expandable body sections).
18
+ *
19
+ * In preview mode: scope, truncated intent, and outcome counts.
20
+ * In drill-in mode: full frontmatter card (scope heading, full intent, side-by-side
21
+ * outcomes) plus expandable body sections cycled with Tab.
22
+ */
23
+ export default function DetailPanel({ file, frontmatter, mode, sections, expandedSection, drilledFilePath, }: DetailPanelProps): React.ReactElement;
24
+ export {};
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useStdout } from "ink";
3
+ /** Truncate a string to maxLen chars, appending "..." if trimmed. */
4
+ function truncate(s, maxLen) {
5
+ if (s.length <= maxLen)
6
+ return s;
7
+ return s.slice(0, maxLen - 3) + "...";
8
+ }
9
+ /** Bullet-point list of outcome strings. */
10
+ function OutcomeList({ items, color }) {
11
+ return (_jsx(Box, { flexDirection: "column", children: items.map((item, i) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: color, children: "\u2022 " }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { wrap: "wrap", children: item }) })] }, i))) }));
12
+ }
13
+ /** Side-by-side (or stacked) outcomes display. */
14
+ function OutcomesDisplay({ desired, undesired, wide, }) {
15
+ if (wide) {
16
+ return (_jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsxs(Box, { flexDirection: "column", flexBasis: "50%", borderStyle: "single", borderColor: "green", paddingX: 1, marginRight: 1, children: [_jsx(Text, { bold: true, color: "green", children: "Desired Outcomes" }), _jsx(Box, { marginTop: 1, children: _jsx(OutcomeList, { items: desired, color: "green" }) })] }), _jsxs(Box, { flexDirection: "column", flexBasis: "50%", borderStyle: "single", borderColor: "red", paddingX: 1, children: [_jsx(Text, { bold: true, color: "red", children: "Undesired Outcomes" }), _jsx(Box, { marginTop: 1, children: _jsx(OutcomeList, { items: undesired, color: "red" }) })] })] }));
17
+ }
18
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "green", paddingX: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, color: "green", children: "Desired Outcomes" }), _jsx(Box, { marginTop: 1, children: _jsx(OutcomeList, { items: desired, color: "green" }) })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "red", paddingX: 1, children: [_jsx(Text, { bold: true, color: "red", children: "Undesired Outcomes" }), _jsx(Box, { marginTop: 1, children: _jsx(OutcomeList, { items: undesired, color: "red" }) })] })] }));
19
+ }
20
+ /**
21
+ * Right-panel component that handles both preview mode (tree navigation) and
22
+ * drill-in mode (formatted frontmatter card with expandable body sections).
23
+ *
24
+ * In preview mode: scope, truncated intent, and outcome counts.
25
+ * In drill-in mode: full frontmatter card (scope heading, full intent, side-by-side
26
+ * outcomes) plus expandable body sections cycled with Tab.
27
+ */
28
+ export default function DetailPanel({ file, frontmatter, mode, sections, expandedSection, drilledFilePath, }) {
29
+ const { stdout } = useStdout();
30
+ const columns = stdout?.columns ?? 80;
31
+ const wide = columns >= 80;
32
+ // --- Preview mode ---
33
+ if (mode === "preview") {
34
+ if (!file) {
35
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Text, { color: "gray", children: "Select a file to preview" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Type to search, [tab] for deep view" }) })] }));
36
+ }
37
+ if (!frontmatter) {
38
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Text, { color: "red", children: "[FAILED TO PARSE]" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: file.relativePath }) })] }));
39
+ }
40
+ const scope = frontmatter.scope ?? "[FAILED TO PARSE]";
41
+ const intent = frontmatter.intent ? truncate(frontmatter.intent, 160) : "[FAILED TO PARSE]";
42
+ const desiredCount = frontmatter.outcomes?.desired.length ?? 0;
43
+ const undesiredCount = frontmatter.outcomes?.undesired.length ?? 0;
44
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, children: ["scope: ", scope] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { wrap: "wrap", children: intent }) }), (desiredCount > 0 || undesiredCount > 0) && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: "green", children: ["\u2713 desired (", desiredCount, ")"] }), _jsxs(Text, { color: "red", children: ["\u2717 undesired (", undesiredCount, ")"] })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "[\u2191\u2193] navigate [enter] drill in [tab] deep view" }) })] }));
45
+ }
46
+ // --- Drill-in mode ---
47
+ const title = drilledFilePath ?? "[unknown]";
48
+ const scope = frontmatter?.scope ?? "[FAILED TO PARSE]";
49
+ const intent = frontmatter?.intent ?? "[FAILED TO PARSE]";
50
+ const desired = frontmatter?.outcomes?.desired ?? [];
51
+ const undesired = frontmatter?.outcomes?.undesired ?? [];
52
+ const visibleSections = sections.filter((s) => s.heading !== "");
53
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, flexGrow: 1, children: [_jsx(Text, { bold: true, children: title }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "scope: " }), _jsx(Text, { children: scope })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { wrap: "wrap", children: intent }) }), (desired.length > 0 || undesired.length > 0) && (_jsx(OutcomesDisplay, { desired: desired, undesired: undesired, wide: wide })), visibleSections.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: visibleSections.map((section, i) => {
54
+ const isExpanded = i === expandedSection;
55
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [isExpanded ? "▾" : "▸", " ", section.heading, !isExpanded && section.summary ? ` (${section.summary})` : ""] }), isExpanded && (_jsx(Box, { marginLeft: 2, marginTop: 1, children: _jsx(Text, { wrap: "wrap", children: section.content }) }))] }, i));
56
+ }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "[esc] back [tab] next section [e] open in editor" }) })] }));
57
+ }
@@ -0,0 +1,24 @@
1
+ import React from "react";
2
+ import type { FlatNode } from "../../cli/flattenTree/index.js";
3
+ interface TreePanelProps {
4
+ /** Pre-filtered visible nodes computed by App (single source of truth). */
5
+ visibleNodes: FlatNode[];
6
+ cursorIndex: number;
7
+ searchFilter: string;
8
+ isDeepView: boolean;
9
+ /** Set of dir paths that are currently expanded. */
10
+ expandedDirs: Set<string>;
11
+ /** True when the cursor is currently on a dir node. */
12
+ cursorOnDir: boolean;
13
+ /** True when the cursor is on a dir node that is currently expanded. */
14
+ cursorDirExpanded: boolean;
15
+ /** Available width in columns for the tree panel content. */
16
+ width: number;
17
+ }
18
+ /**
19
+ * Renders the left-panel tree of .aide files with cursor highlighting and optional summaries.
20
+ * Receives pre-filtered visibleNodes from App — manages no state and performs no filtering.
21
+ * Shows expand/collapse indicators on dir nodes and context-aware footer hints.
22
+ */
23
+ export default function TreePanel({ visibleNodes, cursorIndex, searchFilter, isDeepView, expandedDirs, cursorOnDir, cursorDirExpanded, width, }: TreePanelProps): React.ReactElement;
24
+ export {};