@mrpatronz/nexusflow 0.2.13 → 0.2.18
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/.agents/skills/ccc/SKILL.md +71 -0
- package/.agents/skills/ccc/references/management.md +110 -0
- package/.agents/skills/ccc/references/settings.md +126 -0
- package/.github/workflows/ci.yml +44 -0
- package/.github/workflows/dependabot-merge.yml +53 -0
- package/.github/workflows/release-desktop.yml +67 -0
- package/.github/workflows/release-npm.yml +65 -0
- package/.github/workflows/release-vscode.yml +61 -0
- package/GETTING_STARTED.md +5 -0
- package/README.md +53 -0
- package/desktop/build-installer.js +228 -0
- package/desktop/build.js +118 -0
- package/desktop/installer/nexusflow.iss +35 -0
- package/desktop/neutralino.config.json +39 -0
- package/desktop/package-lock.json +1060 -0
- package/desktop/package.json +14 -0
- package/dist/commands/adapter.d.ts +27 -0
- package/dist/commands/adapter.d.ts.map +1 -0
- package/dist/commands/adapter.js +318 -0
- package/dist/commands/adapter.js.map +1 -0
- package/dist/commands/add-repo.d.ts.map +1 -1
- package/dist/commands/add-repo.js +0 -1
- package/dist/commands/add-repo.js.map +1 -1
- package/dist/commands/commands.test.js +2 -2
- package/dist/commands/commands.test.js.map +1 -1
- package/dist/commands/config.d.ts +22 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +50 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +14 -17
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/desktop.d.ts +12 -0
- package/dist/commands/desktop.d.ts.map +1 -0
- package/dist/commands/desktop.js +119 -0
- package/dist/commands/desktop.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +11 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/refresh.d.ts +1 -0
- package/dist/commands/refresh.d.ts.map +1 -1
- package/dist/commands/refresh.js +2 -14
- package/dist/commands/refresh.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +1 -8
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/tui.d.ts +8 -0
- package/dist/commands/tui.d.ts.map +1 -0
- package/dist/commands/tui.js +439 -0
- package/dist/commands/tui.js.map +1 -0
- package/dist/commands/ui.d.ts +3 -1
- package/dist/commands/ui.d.ts.map +1 -1
- package/dist/commands/ui.js +76 -19
- package/dist/commands/ui.js.map +1 -1
- package/dist/core/adapters/local-storage.d.ts +14 -0
- package/dist/core/adapters/local-storage.d.ts.map +1 -0
- package/dist/core/adapters/local-storage.js +58 -0
- package/dist/core/adapters/local-storage.js.map +1 -0
- package/dist/core/adapters/obsidian-storage.d.ts +28 -0
- package/dist/core/adapters/obsidian-storage.d.ts.map +1 -0
- package/dist/core/adapters/obsidian-storage.js +135 -0
- package/dist/core/adapters/obsidian-storage.js.map +1 -0
- package/dist/core/adapters/registry.d.ts +8 -0
- package/dist/core/adapters/registry.d.ts.map +1 -0
- package/dist/core/adapters/registry.js +37 -0
- package/dist/core/adapters/registry.js.map +1 -0
- package/dist/core/adapters/storage.test.d.ts +2 -0
- package/dist/core/adapters/storage.test.d.ts.map +1 -0
- package/dist/core/adapters/storage.test.js +106 -0
- package/dist/core/adapters/storage.test.js.map +1 -0
- package/dist/core/adapters/vault-storage.d.ts +16 -0
- package/dist/core/adapters/vault-storage.d.ts.map +1 -0
- package/dist/core/adapters/vault-storage.js +77 -0
- package/dist/core/adapters/vault-storage.js.map +1 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +28 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/plugins/index.d.ts +14 -0
- package/dist/core/plugins/index.d.ts.map +1 -0
- package/dist/core/plugins/index.js +2 -0
- package/dist/core/plugins/index.js.map +1 -0
- package/dist/core/plugins/loader.d.ts +2 -0
- package/dist/core/plugins/loader.d.ts.map +1 -0
- package/dist/core/plugins/loader.js +34 -0
- package/dist/core/plugins/loader.js.map +1 -0
- package/dist/core/ports/storage.d.ts +59 -0
- package/dist/core/ports/storage.d.ts.map +1 -0
- package/dist/core/ports/storage.js +10 -0
- package/dist/core/ports/storage.js.map +1 -0
- package/dist/core/storage.d.ts +10 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +29 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +110 -16
- package/dist/core/workspace.js.map +1 -1
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +19 -6
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/diff-context.d.ts +6 -0
- package/dist/generators/diff-context.d.ts.map +1 -0
- package/dist/generators/diff-context.js +68 -0
- package/dist/generators/diff-context.js.map +1 -0
- package/dist/generators/index.d.ts +1 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +75 -18
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/map-generator.d.ts.map +1 -1
- package/dist/generators/map-generator.js +7 -11
- package/dist/generators/map-generator.js.map +1 -1
- package/dist/generators/plan-generator.d.ts.map +1 -1
- package/dist/generators/plan-generator.js +3 -5
- package/dist/generators/plan-generator.js.map +1 -1
- package/dist/gui/app_logo.png +0 -0
- package/dist/gui/assets/index-CB-jWded.css +1 -0
- package/dist/gui/assets/index-DDXpZZu3.js +25 -0
- package/dist/gui/index.html +2 -2
- package/dist/index.js +174 -20
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +212 -28
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +173 -38
- package/dist/server.test.js.map +1 -1
- package/dist/types.d.ts +11 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/git.d.ts +16 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +52 -0
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/local-ai.d.ts.map +1 -1
- package/dist/utils/local-ai.js +7 -3
- package/dist/utils/local-ai.js.map +1 -1
- package/dist/utils/multi-git.d.ts +2 -0
- package/dist/utils/multi-git.d.ts.map +1 -1
- package/dist/utils/multi-git.js +5 -2
- package/dist/utils/multi-git.js.map +1 -1
- package/dist/utils/update-check.d.ts +3 -1
- package/dist/utils/update-check.d.ts.map +1 -1
- package/dist/utils/update-check.js +23 -34
- package/dist/utils/update-check.js.map +1 -1
- package/dist/utils/workflow-advisor.d.ts +16 -0
- package/dist/utils/workflow-advisor.d.ts.map +1 -0
- package/dist/utils/workflow-advisor.js +100 -0
- package/dist/utils/workflow-advisor.js.map +1 -0
- package/dist/utils/workflows.d.ts +11 -0
- package/dist/utils/workflows.d.ts.map +1 -0
- package/dist/utils/workflows.js +156 -0
- package/dist/utils/workflows.js.map +1 -0
- package/dist/utils/workflows.test.d.ts +2 -0
- package/dist/utils/workflows.test.d.ts.map +1 -0
- package/dist/utils/workflows.test.js +86 -0
- package/dist/utils/workflows.test.js.map +1 -0
- package/extension/media/icon.svg +1 -0
- package/extension/package-lock.json +4893 -1660
- package/extension/package.json +58 -11
- package/extension/src/extension.ts +476 -27
- package/extension/tsconfig.json +4 -2
- package/gui/e2e/wizard.spec.ts +19 -0
- package/gui/package-lock.json +342 -381
- package/gui/package.json +12 -12
- package/gui/public/app_logo.png +0 -0
- package/gui/src/App.tsx +991 -81
- package/gui/src/features/workspace/WorkspaceList.tsx +30 -30
- package/gui/src/index.css +64 -22
- package/gui/src/main.tsx +89 -5
- package/package.json +11 -12
- package/resources/workflows/plan-implement-review.md +8 -0
- package/resources/workflows/research-verify.md +6 -0
- package/resources/workflows/solo-developer.md +3 -0
- package/skills-lock.json +11 -0
- package/src/commands/adapter.ts +348 -0
- package/src/commands/add-repo.ts +0 -1
- package/src/commands/commands.test.ts +3 -2
- package/src/commands/config.ts +52 -0
- package/src/commands/create.ts +14 -17
- package/src/commands/desktop.ts +128 -0
- package/src/commands/doctor.ts +13 -0
- package/src/commands/mcp.ts +2 -0
- package/src/commands/refresh.ts +4 -14
- package/src/commands/sync.ts +2 -8
- package/src/commands/tui.ts +484 -0
- package/src/commands/ui.ts +83 -19
- package/src/core/adapters/local-storage.ts +66 -0
- package/src/core/adapters/obsidian-storage.ts +151 -0
- package/src/core/adapters/registry.ts +44 -0
- package/src/core/adapters/storage.test.ts +174 -0
- package/src/core/adapters/vault-storage.ts +86 -0
- package/src/core/config.ts +29 -4
- package/src/core/plugins/index.ts +17 -0
- package/src/core/plugins/loader.ts +36 -0
- package/src/core/ports/storage.ts +71 -0
- package/src/core/storage.ts +37 -0
- package/src/core/workspace.ts +122 -15
- package/src/generators/base.ts +21 -5
- package/src/generators/diff-context.ts +75 -0
- package/src/generators/index.ts +93 -21
- package/src/generators/map-generator.ts +7 -10
- package/src/generators/plan-generator.ts +6 -4
- package/src/index.ts +178 -21
- package/src/server.test.ts +193 -39
- package/src/server.ts +238 -28
- package/src/types.ts +14 -2
- package/src/utils/git.ts +56 -0
- package/src/utils/local-ai.ts +8 -3
- package/src/utils/multi-git.ts +16 -9
- package/src/utils/update-check.ts +42 -31
- package/src/utils/workflow-advisor.ts +118 -0
- package/src/utils/workflows.test.ts +114 -0
- package/src/utils/workflows.ts +178 -0
- package/.github/workflows/release.yml +0 -100
- package/dist/commands/pack.d.ts +0 -7
- package/dist/commands/pack.d.ts.map +0 -1
- package/dist/commands/pack.js +0 -64
- package/dist/commands/pack.js.map +0 -1
- package/dist/core/packer.d.ts +0 -14
- package/dist/core/packer.d.ts.map +0 -1
- package/dist/core/packer.js +0 -87
- package/dist/core/packer.js.map +0 -1
- package/dist/core/packer.test.d.ts +0 -2
- package/dist/core/packer.test.d.ts.map +0 -1
- package/dist/core/packer.test.js +0 -82
- package/dist/core/packer.test.js.map +0 -1
- package/dist/gui/assets/index-CFWwqFux.js +0 -23
- package/dist/gui/assets/index-Dvc9QMvl.css +0 -2
- package/src/commands/pack.ts +0 -73
- package/src/core/packer.test.ts +0 -99
- package/src/core/packer.ts +0 -107
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/ports/storage
|
|
3
|
+
* Storage Port defining the interface for workspace document persistence.
|
|
4
|
+
*
|
|
5
|
+
* Adapters implement this interface to provide different storage backends.
|
|
6
|
+
* Each adapter is self-describing via the `meta` property, which enables
|
|
7
|
+
* automatic discovery in CLI, GUI, and plugin systems.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Describes a single configurable field for an adapter. */
|
|
11
|
+
export interface AdapterConfigField {
|
|
12
|
+
/** Machine-readable key, stored in config.json. */
|
|
13
|
+
key: string;
|
|
14
|
+
/** Human-readable label for CLI prompts and GUI forms. */
|
|
15
|
+
label: string;
|
|
16
|
+
/** Value type — 'path' renders a file picker in the GUI. */
|
|
17
|
+
type: 'string' | 'boolean' | 'number' | 'path';
|
|
18
|
+
/** Whether the field must be set before the adapter can be used. */
|
|
19
|
+
required?: boolean;
|
|
20
|
+
/** Default value when not explicitly configured. */
|
|
21
|
+
default?: unknown;
|
|
22
|
+
/** Help text shown in CLI prompts and GUI tooltips. */
|
|
23
|
+
description?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Metadata that makes an adapter self-describing and discoverable. */
|
|
27
|
+
export interface StorageAdapterMeta {
|
|
28
|
+
/** Unique machine-readable identifier (e.g. 'obsidian'). */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Human-readable name for CLI tables and GUI dropdowns (e.g. 'Obsidian Vault'). */
|
|
31
|
+
displayName: string;
|
|
32
|
+
/** One-line description of what this adapter does. */
|
|
33
|
+
description: string;
|
|
34
|
+
/** Configuration fields this adapter accepts. Empty array = no config needed. */
|
|
35
|
+
configFields: AdapterConfigField[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface StoragePort {
|
|
39
|
+
/** Adapter metadata — who am I, what do I need? */
|
|
40
|
+
readonly meta: StorageAdapterMeta;
|
|
41
|
+
|
|
42
|
+
/** Called once after registration with the user's per-adapter settings. */
|
|
43
|
+
configure?(settings: Record<string, unknown>): void;
|
|
44
|
+
|
|
45
|
+
/** Writes a workspace context/metadata document. */
|
|
46
|
+
writeWorkspaceFile(workspacePath: string, featureId: string, filename: string, content: string): Promise<void>;
|
|
47
|
+
|
|
48
|
+
/** Reads a workspace context/metadata document. */
|
|
49
|
+
readWorkspaceFile(workspacePath: string, featureId: string, filename: string): Promise<string>;
|
|
50
|
+
|
|
51
|
+
/** Checks if a workspace context/metadata document exists. */
|
|
52
|
+
workspaceFileExists(workspacePath: string, featureId: string, filename: string): Promise<boolean>;
|
|
53
|
+
|
|
54
|
+
/** Resolves the human-readable path or link to the workspace document for the AI assistant. */
|
|
55
|
+
resolveWorkspaceFileUrl(workspacePath: string, featureId: string, filename: string): string;
|
|
56
|
+
|
|
57
|
+
/** Writes a base-layer context document (stable maps/conventions across workspaces). */
|
|
58
|
+
writeBaseFile(workspacePath: string, repoName: string, filename: string, content: string): Promise<void>;
|
|
59
|
+
|
|
60
|
+
/** Reads a base-layer context document. */
|
|
61
|
+
readBaseFile(workspacePath: string, repoName: string, filename: string): Promise<string>;
|
|
62
|
+
|
|
63
|
+
/** Checks if a base-layer context document exists. */
|
|
64
|
+
baseFileExists(workspacePath: string, repoName: string, filename: string): Promise<boolean>;
|
|
65
|
+
|
|
66
|
+
/** Resolves the human-readable path or link to the base document. */
|
|
67
|
+
resolveBaseFileUrl(workspacePath: string, repoName: string, filename: string): string;
|
|
68
|
+
|
|
69
|
+
/** Cleanly deletes all workspace-specific documents from the storage backend. */
|
|
70
|
+
deleteWorkspace(workspacePath: string, featureId: string): Promise<void>;
|
|
71
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getActiveStorageProvider } from './adapters/registry.js';
|
|
2
|
+
|
|
3
|
+
export async function writeWorkspaceFile(workspacePath: string, featureId: string, filename: string, content: string): Promise<void> {
|
|
4
|
+
await getActiveStorageProvider().writeWorkspaceFile(workspacePath, featureId, filename, content);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function readWorkspaceFile(workspacePath: string, featureId: string, filename: string): Promise<string> {
|
|
8
|
+
return await getActiveStorageProvider().readWorkspaceFile(workspacePath, featureId, filename);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function workspaceFileExists(workspacePath: string, featureId: string, filename: string): Promise<boolean> {
|
|
12
|
+
return await getActiveStorageProvider().workspaceFileExists(workspacePath, featureId, filename);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function resolveWorkspaceFileUrl(workspacePath: string, featureId: string, filename: string): string {
|
|
16
|
+
return getActiveStorageProvider().resolveWorkspaceFileUrl(workspacePath, featureId, filename);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function writeBaseFile(workspacePath: string, repoName: string, filename: string, content: string): Promise<void> {
|
|
20
|
+
await getActiveStorageProvider().writeBaseFile(workspacePath, repoName, filename, content);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function readBaseFile(workspacePath: string, repoName: string, filename: string): Promise<string> {
|
|
24
|
+
return await getActiveStorageProvider().readBaseFile(workspacePath, repoName, filename);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function baseFileExists(workspacePath: string, repoName: string, filename: string): Promise<boolean> {
|
|
28
|
+
return await getActiveStorageProvider().baseFileExists(workspacePath, repoName, filename);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolveBaseFileUrl(workspacePath: string, repoName: string, filename: string): string {
|
|
32
|
+
return getActiveStorageProvider().resolveBaseFileUrl(workspacePath, repoName, filename);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function deleteWorkspaceFiles(workspacePath: string, featureId: string): Promise<void> {
|
|
36
|
+
await getActiveStorageProvider().deleteWorkspace(workspacePath, featureId);
|
|
37
|
+
}
|
package/src/core/workspace.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import * as fs from 'node:fs/promises';
|
|
9
9
|
import * as path from 'node:path';
|
|
10
|
+
import * as os from 'node:os';
|
|
10
11
|
import { execa } from 'execa';
|
|
11
12
|
|
|
12
13
|
import type { Feature, RepoInfo, WorkspaceContext } from '../types.js';
|
|
@@ -14,8 +15,9 @@ import { createWorktree, removeWorktree } from './worktree.js';
|
|
|
14
15
|
import { detectDefaultBranch } from '../utils/git.js';
|
|
15
16
|
import { analyzeAllRepos } from '../analyzers/index.js';
|
|
16
17
|
import { generateContextFiles } from '../generators/index.js';
|
|
17
|
-
import { packWorkspace } from './packer.js';
|
|
18
18
|
import { loadConfig } from './config.js';
|
|
19
|
+
import { getActiveStorageProvider } from './adapters/registry.js';
|
|
20
|
+
import { deleteWorkspaceFiles } from './storage.js';
|
|
19
21
|
|
|
20
22
|
/** Name of the per-workspace manifest file. */
|
|
21
23
|
const MANIFEST_FILE = 'nexusflow.json';
|
|
@@ -66,17 +68,40 @@ export async function createWorkspace(
|
|
|
66
68
|
console.warn('Warning: Failed to initialize git repository at workspace root:', error);
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
//
|
|
71
|
+
// Generate a .code-workspace file so VS Code opens each repo as a top-level workspace
|
|
72
|
+
// folder with its own SCM provider. Without this, VS Code's git scanner respects the
|
|
73
|
+
// root .gitignore (which lists every repo dir) and never discovers the worktrees.
|
|
74
|
+
try {
|
|
75
|
+
const workspaceName = path.basename(workspacePath);
|
|
76
|
+
const codeWorkspace = {
|
|
77
|
+
folders: [
|
|
78
|
+
{ path: '.', name: `${workspaceName} (workspace)` },
|
|
79
|
+
...repos.map((repo) => ({ path: repo.name, name: repo.name })),
|
|
80
|
+
],
|
|
81
|
+
settings: {
|
|
82
|
+
'search.useIgnoreFiles': false,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
await fs.writeFile(
|
|
86
|
+
path.join(workspacePath, `${workspaceName}.code-workspace`),
|
|
87
|
+
JSON.stringify(codeWorkspace, null, 2) + '\n',
|
|
88
|
+
'utf-8',
|
|
89
|
+
);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.warn('Warning: Failed to create .code-workspace file:', error);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Create .vscode/settings.json for editors that don't use the .code-workspace file
|
|
70
95
|
try {
|
|
71
96
|
const vscodeDir = path.join(workspacePath, '.vscode');
|
|
72
97
|
await fs.mkdir(vscodeDir, { recursive: true });
|
|
73
98
|
const settings = {
|
|
74
|
-
|
|
99
|
+
'search.useIgnoreFiles': false,
|
|
75
100
|
};
|
|
76
101
|
await fs.writeFile(
|
|
77
102
|
path.join(vscodeDir, 'settings.json'),
|
|
78
103
|
JSON.stringify(settings, null, 2) + '\n',
|
|
79
|
-
'utf-8'
|
|
104
|
+
'utf-8',
|
|
80
105
|
);
|
|
81
106
|
} catch (error) {
|
|
82
107
|
console.warn('Warning: Failed to create .vscode/settings.json:', error);
|
|
@@ -90,7 +115,7 @@ export async function createWorkspace(
|
|
|
90
115
|
"mcpServers": {
|
|
91
116
|
"nexusflow": {
|
|
92
117
|
"command": "npx",
|
|
93
|
-
"args": ["-y", "@mrpatronz/nexusflow", "mcp", "
|
|
118
|
+
"args": ["-y", "@mrpatronz/nexusflow", "mcp", "start"]
|
|
94
119
|
}
|
|
95
120
|
}
|
|
96
121
|
};
|
|
@@ -117,6 +142,9 @@ export async function createWorkspace(
|
|
|
117
142
|
// Persist the feature manifest.
|
|
118
143
|
await saveFeatureConfig(workspacePath, feature);
|
|
119
144
|
|
|
145
|
+
// Exclude NexusFlow files from git
|
|
146
|
+
await excludeNexusFlowFiles(workspacePath, feature);
|
|
147
|
+
|
|
120
148
|
return workspacePath;
|
|
121
149
|
}
|
|
122
150
|
|
|
@@ -178,9 +206,9 @@ export async function saveFeatureConfig(
|
|
|
178
206
|
workspacePath: string,
|
|
179
207
|
feature: Feature,
|
|
180
208
|
): Promise<void> {
|
|
181
|
-
const manifestPath = path.join(workspacePath, MANIFEST_FILE);
|
|
182
209
|
const data = JSON.stringify(feature, null, 2) + '\n';
|
|
183
|
-
|
|
210
|
+
const adapter = getActiveStorageProvider();
|
|
211
|
+
await adapter.writeWorkspaceFile(workspacePath, feature.id, MANIFEST_FILE, data);
|
|
184
212
|
}
|
|
185
213
|
|
|
186
214
|
/**
|
|
@@ -194,16 +222,36 @@ export async function saveFeatureConfig(
|
|
|
194
222
|
export async function loadFeatureConfig(
|
|
195
223
|
workspacePath: string,
|
|
196
224
|
): Promise<Feature | null> {
|
|
197
|
-
const
|
|
198
|
-
|
|
225
|
+
const featureId = path.basename(workspacePath);
|
|
226
|
+
const adapter = getActiveStorageProvider();
|
|
199
227
|
|
|
200
|
-
|
|
228
|
+
// 1. Try reading via the storage adapter first
|
|
229
|
+
try {
|
|
230
|
+
const raw = await adapter.readWorkspaceFile(workspacePath, featureId, MANIFEST_FILE);
|
|
231
|
+
return JSON.parse(raw) as Feature;
|
|
232
|
+
} catch {}
|
|
233
|
+
|
|
234
|
+
// 2. Try direct local check
|
|
235
|
+
const manifestPath = path.join(workspacePath, MANIFEST_FILE);
|
|
201
236
|
try {
|
|
202
237
|
const raw = await fs.readFile(manifestPath, 'utf-8');
|
|
203
238
|
return JSON.parse(raw) as Feature;
|
|
204
|
-
} catch {
|
|
205
|
-
|
|
239
|
+
} catch {}
|
|
240
|
+
|
|
241
|
+
// 3. Try direct vault check
|
|
242
|
+
const vaultManifest = path.join(os.homedir(), '.nexusflow', 'vault', featureId, MANIFEST_FILE);
|
|
243
|
+
try {
|
|
244
|
+
const raw = await fs.readFile(vaultManifest, 'utf-8');
|
|
245
|
+
return JSON.parse(raw) as Feature;
|
|
246
|
+
} catch {}
|
|
247
|
+
|
|
248
|
+
// 4. Traverse parent directories
|
|
249
|
+
const rootDir = await findWorkspaceRoot(workspacePath);
|
|
250
|
+
if (rootDir && rootDir !== workspacePath) {
|
|
251
|
+
return loadFeatureConfig(rootDir);
|
|
206
252
|
}
|
|
253
|
+
|
|
254
|
+
return null;
|
|
207
255
|
}
|
|
208
256
|
|
|
209
257
|
/**
|
|
@@ -255,6 +303,12 @@ export async function deleteWorkspace(
|
|
|
255
303
|
): Promise<void> {
|
|
256
304
|
const feature = await loadFeatureConfig(workspacePath);
|
|
257
305
|
if (feature) {
|
|
306
|
+
try {
|
|
307
|
+
await deleteWorkspaceFiles(workspacePath, feature.id);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.warn(`Warning: failed to delete workspace context files from storage:`, error);
|
|
310
|
+
}
|
|
311
|
+
|
|
258
312
|
const origRepos = feature.originalRepos || [];
|
|
259
313
|
for (let i = 0; i < feature.repos.length; i++) {
|
|
260
314
|
const worktreePath = feature.repos[i]!;
|
|
@@ -373,8 +427,61 @@ export async function addRepoToWorkspace(
|
|
|
373
427
|
};
|
|
374
428
|
|
|
375
429
|
await generateContextFiles(ctx, feature.assistants, workspacePath);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
430
|
+
await excludeNexusFlowFiles(workspacePath, feature);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Automatically adds NexusFlow workspace context and config files to git's local
|
|
435
|
+
* exclude list for each repository so they never show up in `git status` or get committed.
|
|
436
|
+
*/
|
|
437
|
+
async function excludeNexusFlowFiles(workspacePath: string, feature: Feature): Promise<void> {
|
|
438
|
+
const excludeEntries = [
|
|
439
|
+
'CLAUDE.md',
|
|
440
|
+
'AGENTS.md',
|
|
441
|
+
'WORKSPACE.md',
|
|
442
|
+
'nexusflow-knowledge.md',
|
|
443
|
+
'nexusflow-plan.md',
|
|
444
|
+
'nexusflow-conventions-*.md',
|
|
445
|
+
'nexusflow-map-*.md',
|
|
446
|
+
'nexusflow-diff-context.md',
|
|
447
|
+
'.cursor/rules/',
|
|
448
|
+
'.vscode/settings.json'
|
|
449
|
+
];
|
|
450
|
+
|
|
451
|
+
for (const repoPath of feature.repos) {
|
|
452
|
+
try {
|
|
453
|
+
const gitDir = path.join(repoPath, '.git');
|
|
454
|
+
let worktreeGitDir = gitDir;
|
|
455
|
+
const stat = await fs.stat(gitDir);
|
|
456
|
+
if (stat.isFile()) {
|
|
457
|
+
const fileContent = await fs.readFile(gitDir, 'utf8');
|
|
458
|
+
const match = fileContent.match(/gitdir:\s*(.+)/);
|
|
459
|
+
if (match) {
|
|
460
|
+
worktreeGitDir = match[1].trim();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const excludeFilePath = path.join(worktreeGitDir, 'info', 'exclude');
|
|
465
|
+
await fs.mkdir(path.dirname(excludeFilePath), { recursive: true });
|
|
466
|
+
|
|
467
|
+
let excludeContent = '';
|
|
468
|
+
try {
|
|
469
|
+
excludeContent = await fs.readFile(excludeFilePath, 'utf8');
|
|
470
|
+
} catch {}
|
|
471
|
+
|
|
472
|
+
let updated = false;
|
|
473
|
+
for (const entry of excludeEntries) {
|
|
474
|
+
if (!excludeContent.includes(entry)) {
|
|
475
|
+
excludeContent = excludeContent.trim() + '\n' + entry + '\n';
|
|
476
|
+
updated = true;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (updated) {
|
|
481
|
+
await fs.writeFile(excludeFilePath, excludeContent.trim() + '\n', 'utf8');
|
|
482
|
+
}
|
|
483
|
+
} catch {
|
|
484
|
+
// Ignore if not a git repo or directory missing
|
|
485
|
+
}
|
|
379
486
|
}
|
|
380
487
|
}
|
package/src/generators/base.ts
CHANGED
|
@@ -8,6 +8,7 @@ import * as path from 'node:path';
|
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
9
|
import * as os from 'node:os';
|
|
10
10
|
import type { WorkspaceContext, ProjectAnalysis } from '../types.js';
|
|
11
|
+
import { resolveBaseFileUrl, resolveWorkspaceFileUrl } from '../core/storage.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Formats a ProjectAnalysis into a readable markdown section.
|
|
@@ -17,9 +18,12 @@ function formatProjectSection(analysis: ProjectAnalysis, workspacePath: string):
|
|
|
17
18
|
|
|
18
19
|
lines.push(`### ${analysis.name}`);
|
|
19
20
|
|
|
20
|
-
const mapPath =
|
|
21
|
+
const mapPath = resolveBaseFileUrl(workspacePath, analysis.name, `nexusflow-map-${analysis.name}.md`);
|
|
21
22
|
lines.push(`- **Architecture Map**: [nexusflow-map-${analysis.name}.md](file:///${mapPath}) — **Instruction**: Before modifying this repository, read its architecture map. For exploration, consult the map's section index on demand.`);
|
|
22
23
|
|
|
24
|
+
const baseKnowledgePath = resolveBaseFileUrl(workspacePath, analysis.name, 'nexusflow-knowledge.md');
|
|
25
|
+
lines.push(`- **Base Knowledge & Decisions**: [nexusflow-knowledge.md](file:///${baseKnowledgePath}) — **Instruction**: Read this for persistent codebase learnings, decisions, and conventions.`);
|
|
26
|
+
|
|
23
27
|
// Tech stack
|
|
24
28
|
const { techStack } = analysis;
|
|
25
29
|
if (techStack.languages.length > 0 && techStack.languages[0] !== 'other') {
|
|
@@ -151,19 +155,30 @@ ${parts.join('\n')}
|
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
const knowledgePath =
|
|
158
|
+
const knowledgePath = resolveWorkspaceFileUrl(workspacePath, feature.id, 'nexusflow-knowledge.md').replace(/\\/g, '/');
|
|
155
159
|
|
|
156
160
|
let setupDone = false;
|
|
157
161
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const content = fs.readFileSync(realKnowledgePath, 'utf-8');
|
|
162
|
+
if (fs.existsSync(knowledgePath)) {
|
|
163
|
+
const content = fs.readFileSync(knowledgePath, 'utf-8');
|
|
161
164
|
if (!content.includes('No assumptions recorded yet') && !content.includes('AI assistant to populate')) {
|
|
162
165
|
setupDone = true;
|
|
163
166
|
}
|
|
164
167
|
}
|
|
165
168
|
} catch {}
|
|
166
169
|
|
|
170
|
+
// Teamwork strategy section
|
|
171
|
+
let teamworkSection = '';
|
|
172
|
+
if (feature.teamworkInstructions) {
|
|
173
|
+
teamworkSection = `
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Team Collaboration Strategy
|
|
177
|
+
|
|
178
|
+
${feature.teamworkInstructions}
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
|
|
167
182
|
const taskSection = setupDone
|
|
168
183
|
? `## Setup Status\n\n✅ **Setup Completed**: Project assumptions and initial questions have been addressed. Refer to [nexusflow-knowledge.md](file:///${knowledgePath}) for persistent session details.`
|
|
169
184
|
: `## First Steps\n\nYour very first task upon entering this workspace is to explore the codebase and align with the user:\n\n1. **Verify Assumptions**: Open [nexusflow-knowledge.md](file:///${knowledgePath}) and fill in the **Project Assumptions** section with a brief description of what each project does, its tech stack, and responsibilities.\n2. **Raise Questions**: Document any outstanding uncertainties or architectural questions in the **Clarifying Questions for the User** section.\n3. **Obtain Approval**: Ask the user to confirm your assumptions and answer your questions before writing any code.`;
|
|
@@ -189,6 +204,7 @@ ${resumptionSection}
|
|
|
189
204
|
---
|
|
190
205
|
|
|
191
206
|
${taskSection}
|
|
207
|
+
${teamworkSection}
|
|
192
208
|
---
|
|
193
209
|
|
|
194
210
|
## Guidelines
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
import type { WorkspaceContext } from '../types.js';
|
|
5
|
+
import { getActiveStorageProvider } from '../core/adapters/registry.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generates an incremental task context file listing only changed files on the branch.
|
|
9
|
+
*/
|
|
10
|
+
export async function generateDiffContext(
|
|
11
|
+
ctx: WorkspaceContext,
|
|
12
|
+
workspacePath: string
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
let content = `# Incremental Task Context (Git Diff)\n\n`;
|
|
15
|
+
content += `This file lists the files modified or added on this feature branch compared to the base branch. Use this to focus the AI's attention.\n\n`;
|
|
16
|
+
|
|
17
|
+
let hasDiff = false;
|
|
18
|
+
|
|
19
|
+
for (const repo of ctx.repos) {
|
|
20
|
+
const repoPath = path.join(workspacePath, repo.name);
|
|
21
|
+
try {
|
|
22
|
+
await fs.access(repoPath);
|
|
23
|
+
} catch {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const defaultBranch = repo.defaultBranch || 'main';
|
|
28
|
+
|
|
29
|
+
let diffFiles: string[] = [];
|
|
30
|
+
try {
|
|
31
|
+
const { stdout } = await execa('git', ['diff', `origin/${defaultBranch}...HEAD`, '--name-only'], {
|
|
32
|
+
cwd: repoPath,
|
|
33
|
+
shell: process.platform === 'win32',
|
|
34
|
+
});
|
|
35
|
+
diffFiles = stdout.split('\n').map(f => f.trim()).filter(Boolean);
|
|
36
|
+
} catch {
|
|
37
|
+
try {
|
|
38
|
+
const { stdout } = await execa('git', ['diff', `${defaultBranch}...HEAD`, '--name-only'], {
|
|
39
|
+
cwd: repoPath,
|
|
40
|
+
shell: process.platform === 'win32',
|
|
41
|
+
});
|
|
42
|
+
diffFiles = stdout.split('\n').map(f => f.trim()).filter(Boolean);
|
|
43
|
+
} catch {
|
|
44
|
+
try {
|
|
45
|
+
const { stdout } = await execa('git', ['status', '--porcelain'], {
|
|
46
|
+
cwd: repoPath,
|
|
47
|
+
shell: process.platform === 'win32',
|
|
48
|
+
});
|
|
49
|
+
diffFiles = stdout.split('\n')
|
|
50
|
+
.map(line => line.slice(3).trim())
|
|
51
|
+
.filter(Boolean);
|
|
52
|
+
} catch {
|
|
53
|
+
// git command failed or not a repo
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (diffFiles.length > 0) {
|
|
59
|
+
hasDiff = true;
|
|
60
|
+
content += `## Repository: ${repo.name}\n\n`;
|
|
61
|
+
for (const file of diffFiles) {
|
|
62
|
+
const absoluteFilePath = path.join(repoPath, file).replace(/\\/g, '/');
|
|
63
|
+
content += `* [${file}](file:///${absoluteFilePath})\n`;
|
|
64
|
+
}
|
|
65
|
+
content += `\n`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!hasDiff) {
|
|
70
|
+
content += `*No changed files detected on this branch yet.*\n`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const storage = getActiveStorageProvider();
|
|
74
|
+
await storage.writeWorkspaceFile(workspacePath, ctx.feature.id, 'nexusflow-diff-context.md', content);
|
|
75
|
+
}
|
package/src/generators/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ import { generateCursorConfig } from './cursor.js';
|
|
|
10
10
|
import { buildContextContent } from './base.js';
|
|
11
11
|
import { generateImplementationPlan } from './plan-generator.js';
|
|
12
12
|
import { generateSkills } from './skills-generator.js';
|
|
13
|
+
import { writeWorkspaceFile, workspaceFileExists, writeBaseFile, readBaseFile, baseFileExists } from '../core/storage.js';
|
|
14
|
+
import { generateDiffContext } from './diff-context.js';
|
|
13
15
|
|
|
14
16
|
/** Maps each assistant to its generator function and the file it produces. */
|
|
15
17
|
const GENERATORS: Record<
|
|
@@ -102,6 +104,34 @@ _(No gotchas recorded yet.)_
|
|
|
102
104
|
`;
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Builds the base codebase knowledge starter content.
|
|
109
|
+
*/
|
|
110
|
+
function buildBaseKnowledgeContent(repoName: string): string {
|
|
111
|
+
return `# Base Codebase Knowledge — ${repoName}
|
|
112
|
+
|
|
113
|
+
> **This is a persistent cross-session memory file for ${repoName}.**
|
|
114
|
+
> AI assistants should read this at the start of each session, and append new architectural learnings, conventions, decisions, and gotchas at the end.
|
|
115
|
+
> This file persists across all features and tasks for this repository.
|
|
116
|
+
|
|
117
|
+
## Architecture & Domain Concepts
|
|
118
|
+
<!-- Document major design patterns, domain layers, and system concepts here. -->
|
|
119
|
+
- None recorded yet.
|
|
120
|
+
|
|
121
|
+
## Coding Conventions & Invariants
|
|
122
|
+
<!-- Document coding styles, library rules, and invariants that must not be broken. -->
|
|
123
|
+
- None recorded yet.
|
|
124
|
+
|
|
125
|
+
## Discovered Gotchas & Watch-outs
|
|
126
|
+
<!-- Document weird behaviors, workarounds, or bugs to avoid repeating them. -->
|
|
127
|
+
- None recorded yet.
|
|
128
|
+
|
|
129
|
+
## Architecture Decisions
|
|
130
|
+
<!-- Document key architectural decisions made for this repository over time. -->
|
|
131
|
+
- None recorded yet.
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
|
|
105
135
|
/**
|
|
106
136
|
* Generates AI context files for each of the selected assistants.
|
|
107
137
|
*
|
|
@@ -119,32 +149,70 @@ export async function generateContextFiles(
|
|
|
119
149
|
assistants: AIAssistant[],
|
|
120
150
|
workspacePath: string,
|
|
121
151
|
onlyRepo?: string,
|
|
152
|
+
onlyBase?: boolean,
|
|
122
153
|
): Promise<void> {
|
|
123
154
|
// Always generate a universal WORKSPACE.md at the workspace root
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
155
|
+
if (!onlyBase) {
|
|
156
|
+
try {
|
|
157
|
+
const content = buildContextContent(ctx);
|
|
158
|
+
await writeWorkspaceFile(workspacePath, ctx.feature.id, 'WORKSPACE.md', content);
|
|
159
|
+
console.log(
|
|
160
|
+
chalk.green(' ✔'),
|
|
161
|
+
`Generated universal ${chalk.bold('WORKSPACE.md')}`,
|
|
162
|
+
);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
165
|
+
console.error(
|
|
166
|
+
chalk.red(' ✖'),
|
|
167
|
+
`Failed to generate universal WORKSPACE.md: ${message}`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
132
171
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
172
|
+
// Generate base knowledge for each repo if it does not exist (or force recreate if onlyBase is true)
|
|
173
|
+
for (const repo of ctx.repos) {
|
|
174
|
+
if (onlyRepo && repo.name !== onlyRepo) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const baseExists = await baseFileExists(workspacePath, repo.name, 'nexusflow-knowledge.md');
|
|
178
|
+
if (!baseExists || onlyBase) {
|
|
179
|
+
const baseContent = buildBaseKnowledgeContent(repo.name);
|
|
180
|
+
await writeBaseFile(workspacePath, repo.name, 'nexusflow-knowledge.md', baseContent);
|
|
181
|
+
console.log(
|
|
182
|
+
chalk.green(' ✔'),
|
|
183
|
+
`Generated base knowledge for ${chalk.bold(repo.name)}`,
|
|
184
|
+
);
|
|
139
185
|
} else {
|
|
140
|
-
console.log(
|
|
186
|
+
console.log(
|
|
187
|
+
chalk.gray(' ○'),
|
|
188
|
+
`Base knowledge for ${repo.name} already exists — preserving`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!onlyBase) {
|
|
194
|
+
try {
|
|
195
|
+
// Generate nexusflow-knowledge.md if it does not exist
|
|
196
|
+
const knowledgeExists = await workspaceFileExists(workspacePath, ctx.feature.id, 'nexusflow-knowledge.md');
|
|
197
|
+
if (!knowledgeExists) {
|
|
198
|
+
const knowledgeContent = buildKnowledgeContent(ctx);
|
|
199
|
+
await writeWorkspaceFile(workspacePath, ctx.feature.id, 'nexusflow-knowledge.md', knowledgeContent);
|
|
200
|
+
console.log(chalk.green(' ✔'), `Generated ${chalk.bold('nexusflow-knowledge.md')} (persistent AI memory)`);
|
|
201
|
+
} else {
|
|
202
|
+
console.log(chalk.gray(' ○'), `nexusflow-knowledge.md already exists — preserving existing content`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Generate Git branch diff context file
|
|
206
|
+
await generateDiffContext(ctx, workspacePath);
|
|
207
|
+
console.log(chalk.green(' ✔'), `Generated ${chalk.bold('nexusflow-diff-context.md')} (incremental git diff)`);
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
211
|
+
console.error(
|
|
212
|
+
chalk.red(' ✖'),
|
|
213
|
+
`Failed to generate workspace context files: ${message}`,
|
|
214
|
+
);
|
|
141
215
|
}
|
|
142
|
-
} catch (error) {
|
|
143
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
-
console.error(
|
|
145
|
-
chalk.red(' ✖'),
|
|
146
|
-
`Failed to generate universal context files: ${message}`,
|
|
147
|
-
);
|
|
148
216
|
}
|
|
149
217
|
|
|
150
218
|
// Generate per-repo architecture maps
|
|
@@ -178,6 +246,10 @@ export async function generateContextFiles(
|
|
|
178
246
|
}
|
|
179
247
|
}
|
|
180
248
|
|
|
249
|
+
if (onlyBase) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
181
253
|
// Generate configuration for each selected assistant
|
|
182
254
|
for (const assistant of assistants) {
|
|
183
255
|
const entry = GENERATORS[assistant];
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import * as fs from 'node:fs/promises';
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import { globby } from 'globby';
|
|
10
|
+
import { writeWorkspaceFile, readWorkspaceFile, workspaceFileExists, writeBaseFile, readBaseFile, baseFileExists } from '../core/storage.js';
|
|
10
11
|
import type { ProjectAnalysis, RepoInfo } from '../types.js';
|
|
11
12
|
|
|
12
13
|
interface PatternRule {
|
|
@@ -493,14 +494,11 @@ export async function generateRepoMap(
|
|
|
493
494
|
md.push('## 📝 Discovered Conventions');
|
|
494
495
|
md.push('');
|
|
495
496
|
|
|
496
|
-
const
|
|
497
|
+
const featureId = path.basename(workspacePath);
|
|
498
|
+
const conventionsFilename = `nexusflow-conventions-${repoName}.md`;
|
|
497
499
|
let customConventions = '';
|
|
498
500
|
|
|
499
|
-
|
|
500
|
-
try {
|
|
501
|
-
await fs.access(conventionsFile);
|
|
502
|
-
conventionsExist = true;
|
|
503
|
-
} catch {}
|
|
501
|
+
const conventionsExist = await workspaceFileExists(workspacePath, featureId, conventionsFilename);
|
|
504
502
|
|
|
505
503
|
if (!conventionsExist) {
|
|
506
504
|
const starterContent = `# Repository Conventions — ${repoName}
|
|
@@ -519,12 +517,12 @@ Future AI assistant sessions will read these conventions from the repository map
|
|
|
519
517
|
- None recorded yet.
|
|
520
518
|
`;
|
|
521
519
|
try {
|
|
522
|
-
await
|
|
520
|
+
await writeBaseFile(workspacePath, repoName, conventionsFilename, starterContent);
|
|
523
521
|
customConventions = starterContent.replace(/^#\s+.+\n?/, '').trim();
|
|
524
522
|
} catch {}
|
|
525
523
|
} else {
|
|
526
524
|
try {
|
|
527
|
-
customConventions = await
|
|
525
|
+
customConventions = await readBaseFile(workspacePath, repoName, conventionsFilename);
|
|
528
526
|
customConventions = customConventions.replace(/^#\s+.+\n?/, '').trim();
|
|
529
527
|
} catch {}
|
|
530
528
|
}
|
|
@@ -537,6 +535,5 @@ Future AI assistant sessions will read these conventions from the repository map
|
|
|
537
535
|
md.push('');
|
|
538
536
|
}
|
|
539
537
|
|
|
540
|
-
|
|
541
|
-
await fs.writeFile(outPath, md.join('\n'), 'utf-8');
|
|
538
|
+
await writeBaseFile(workspacePath, repoName, `nexusflow-map-${repoName}.md`, md.join('\n'));
|
|
542
539
|
}
|