@chamba/core 0.3.1 → 0.4.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/dist/index.d.ts +53 -1
- package/dist/index.js +185 -55
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -420,6 +420,40 @@ declare class VaultWriter {
|
|
|
420
420
|
write(input: WriteNoteInput): Promise<WriteNoteResult>;
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
interface RuleConvention {
|
|
424
|
+
editor: string;
|
|
425
|
+
/** Path relative to a repo root. */
|
|
426
|
+
path: string;
|
|
427
|
+
kind: 'file' | 'dir';
|
|
428
|
+
}
|
|
429
|
+
declare const RULE_SOURCES: readonly RuleConvention[];
|
|
430
|
+
/** A concrete rule file found in a repo. */
|
|
431
|
+
interface RuleSource {
|
|
432
|
+
/** Repo path relative to the workspace root (`.` for the root itself). */
|
|
433
|
+
repo: string;
|
|
434
|
+
editor: string;
|
|
435
|
+
/** Rule file path relative to the workspace root. */
|
|
436
|
+
path: string;
|
|
437
|
+
}
|
|
438
|
+
interface RuleExcerpt {
|
|
439
|
+
source: RuleSource;
|
|
440
|
+
excerpt: string;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Discover coding-rule files across the workspace root and each repo, for every
|
|
444
|
+
* editor convention in the catalog. Non-exclusive: a repo can match several.
|
|
445
|
+
* `repoDirs` are paths relative to the workspace root (`.` for the root).
|
|
446
|
+
*/
|
|
447
|
+
declare function detectRuleSources(fs: FilesystemPort, workspaceRoot: string, repoDirs: string[]): Promise<RuleSource[]>;
|
|
448
|
+
/**
|
|
449
|
+
* Read each rule file fresh and return a clamped excerpt, bounded by a total
|
|
450
|
+
* budget so rules never crowd out the rest of the context.
|
|
451
|
+
*/
|
|
452
|
+
declare function readRuleExcerpts(fs: FilesystemPort, workspaceRoot: string, sources: RuleSource[], opts?: {
|
|
453
|
+
maxCharsPerRule?: number;
|
|
454
|
+
totalBudget?: number;
|
|
455
|
+
}): Promise<RuleExcerpt[]>;
|
|
456
|
+
|
|
423
457
|
/** Relative path (from workspace root) of the chamba workspace file. */
|
|
424
458
|
declare const WORKSPACE_DIR = ".chamba";
|
|
425
459
|
declare const WORKSPACE_FILE = "workspace.md";
|
|
@@ -438,6 +472,8 @@ interface Workspace {
|
|
|
438
472
|
framework?: string;
|
|
439
473
|
conventions: string[];
|
|
440
474
|
projects: ProjectRef[];
|
|
475
|
+
/** Coding-rule files found across repos (Cursor, Claude, Trae, …). */
|
|
476
|
+
ruleSources: RuleSource[];
|
|
441
477
|
/** Top-level directory names (without trailing slash), sorted. */
|
|
442
478
|
folderMap: string[];
|
|
443
479
|
}
|
|
@@ -588,6 +624,8 @@ interface ContextBuildInput {
|
|
|
588
624
|
task: string;
|
|
589
625
|
/** When set, search this Obsidian vault for notes relevant to the task. */
|
|
590
626
|
vaultPath?: string;
|
|
627
|
+
/** Include a section with each repo's coding rules (default true). */
|
|
628
|
+
includeRules?: boolean;
|
|
591
629
|
/** Soft cap on the produced context, in estimated tokens (~4 chars/token). */
|
|
592
630
|
maxTokens?: number;
|
|
593
631
|
}
|
|
@@ -604,12 +642,19 @@ declare class ContextBuilder {
|
|
|
604
642
|
private readonly fs;
|
|
605
643
|
constructor(fs: FilesystemPort);
|
|
606
644
|
build(input: ContextBuildInput): Promise<BuiltContext>;
|
|
645
|
+
/** Each repo's coding rules (read fresh, clamped), non-exclusive across editors. */
|
|
646
|
+
private codingRulesSection;
|
|
607
647
|
private workspaceSection;
|
|
608
648
|
private notesSection;
|
|
609
649
|
private searchNotes;
|
|
610
650
|
private collectMarkdown;
|
|
611
651
|
private tryRead;
|
|
612
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* All markdown notes under a vault (the same set `chamba_load_context` searches),
|
|
655
|
+
* skipping `.obsidian`, `.trash`, etc. Useful to show what chamba actually sees.
|
|
656
|
+
*/
|
|
657
|
+
declare function listVaultNotes(fs: FilesystemPort, root: string): Promise<string[]>;
|
|
613
658
|
|
|
614
659
|
/**
|
|
615
660
|
* Line-based diff (LCS) producing a readable unified-ish output:
|
|
@@ -627,6 +672,13 @@ interface VaultDetection {
|
|
|
627
672
|
path?: string;
|
|
628
673
|
noteCount?: number;
|
|
629
674
|
}
|
|
675
|
+
/**
|
|
676
|
+
* The vault is the directory that *contains* `.obsidian` — never `.obsidian`
|
|
677
|
+
* itself. A common mistake is to point `CHAMBA_OBSIDIAN_VAULT_PATH` at the
|
|
678
|
+
* `.obsidian` folder; correct it to its parent so writes and note search land
|
|
679
|
+
* in the real vault.
|
|
680
|
+
*/
|
|
681
|
+
declare function normalizeVaultPath(path: string): string;
|
|
630
682
|
interface DetectOptions {
|
|
631
683
|
/** Explicit vault path (e.g. from CHAMBA_OBSIDIAN_VAULT_PATH). Authoritative. */
|
|
632
684
|
explicitPath?: string;
|
|
@@ -835,4 +887,4 @@ declare class MultiRepoWorktreeManager {
|
|
|
835
887
|
private git;
|
|
836
888
|
}
|
|
837
889
|
|
|
838
|
-
export { AGENT_ROLES, type AgentConfig, type AgentRole, type BranchNameInput, type BuiltContext, type ChambaConfig, type CleanupMultiResult, type CleanupResult, type ClockPort, ConfigError, type ConfigFile, type ConfigSource, type ConfigSourceKind, type ContextBuildInput, ContextBuilder, type CreateMultiInput, type CreateWorktreeInput, DEFAULT_CONFIG, DEFAULT_WORKTREE_CONFIG, type DetectOptions, type DirEntry, EFFORT_LEVELS, type Effort, FakeProcess, FilesystemMemoryStore, type FilesystemPort, type GeneratePlanInput, GitDetector, type Issue, type IssueSeverity, type ListedWorktree, type LoadConfigOptions, type LoadConfigResult, MEMORY_DIR, MODEL_CATALOG, type Memory, MemoryFilesystem, type MemoryStore, type ModelInfo, type ModelProvider, MultiRepoWorktreeManager, type MultiRepoWorktreeResult, type NoteFields, ObsidianDetector, type ParseResult, type PartialWorktreeConfig, type PlanReview, type PlanWorktreesInput, type ProcessExecOptions, type ProcessHandler, type ProcessPort, type ProcessResult, type ProjectRef, REASONING_PRIORITIES, ROLE_DESCRIPTIONS, type ReasoningPriority, type RecordedCall, type RelevantNote, type RememberInput, type ResolvedConfig, type ReviewInput, Reviewer, type SubtaskSpec, VAULT_NOTES_DIR, type ValidatePlanInput, type ValidationResult, type VaultDetection, VaultWriter, WORKSPACE_DIR, WORKSPACE_FILE, WORKSPACE_RELATIVE_PATH, type WorkerKind, type Workspace, WorkspaceScanner, type WorktreeConfig, WorktreeError, type WorktreeHandle, type WorktreeLayout, WorktreeManager, type WorktreePlanItem, type WorktreeStatus, type WriteNoteInput, type WriteNoteResult, basename, buildBranchName, buildHint, buildTicketBranch, configFileSchema, copyEnvFiles, detectGitRepos, diffLines, dirname, editorWorkspaceContent, editorWorkspaceDir, extname, generatePlanTemplate, getModel, joinPath, loadConfig, modelsByProvider, parseChambaConfig, planWorktrees, renderNote, renderWorkspaceMarkdown, resolveEffort, resolveRole, resolveWorktreeConfig, safeTicket, slugify, slugifyForGit, suggestFilesLikelyTouched, suggestSubtasks, textsEqual, validatePlan, worktreeConfigSchema, worktreePathFor, worktreeRelativePath, writeEditorWorkspace };
|
|
890
|
+
export { AGENT_ROLES, type AgentConfig, type AgentRole, type BranchNameInput, type BuiltContext, type ChambaConfig, type CleanupMultiResult, type CleanupResult, type ClockPort, ConfigError, type ConfigFile, type ConfigSource, type ConfigSourceKind, type ContextBuildInput, ContextBuilder, type CreateMultiInput, type CreateWorktreeInput, DEFAULT_CONFIG, DEFAULT_WORKTREE_CONFIG, type DetectOptions, type DirEntry, EFFORT_LEVELS, type Effort, FakeProcess, FilesystemMemoryStore, type FilesystemPort, type GeneratePlanInput, GitDetector, type Issue, type IssueSeverity, type ListedWorktree, type LoadConfigOptions, type LoadConfigResult, MEMORY_DIR, MODEL_CATALOG, type Memory, MemoryFilesystem, type MemoryStore, type ModelInfo, type ModelProvider, MultiRepoWorktreeManager, type MultiRepoWorktreeResult, type NoteFields, ObsidianDetector, type ParseResult, type PartialWorktreeConfig, type PlanReview, type PlanWorktreesInput, type ProcessExecOptions, type ProcessHandler, type ProcessPort, type ProcessResult, type ProjectRef, REASONING_PRIORITIES, ROLE_DESCRIPTIONS, RULE_SOURCES, type ReasoningPriority, type RecordedCall, type RelevantNote, type RememberInput, type ResolvedConfig, type ReviewInput, Reviewer, type RuleConvention, type RuleExcerpt, type RuleSource, type SubtaskSpec, VAULT_NOTES_DIR, type ValidatePlanInput, type ValidationResult, type VaultDetection, VaultWriter, WORKSPACE_DIR, WORKSPACE_FILE, WORKSPACE_RELATIVE_PATH, type WorkerKind, type Workspace, WorkspaceScanner, type WorktreeConfig, WorktreeError, type WorktreeHandle, type WorktreeLayout, WorktreeManager, type WorktreePlanItem, type WorktreeStatus, type WriteNoteInput, type WriteNoteResult, basename, buildBranchName, buildHint, buildTicketBranch, configFileSchema, copyEnvFiles, detectGitRepos, detectRuleSources, diffLines, dirname, editorWorkspaceContent, editorWorkspaceDir, extname, generatePlanTemplate, getModel, joinPath, listVaultNotes, loadConfig, modelsByProvider, normalizeVaultPath, parseChambaConfig, planWorktrees, readRuleExcerpts, renderNote, renderWorkspaceMarkdown, resolveEffort, resolveRole, resolveWorktreeConfig, safeTicket, slugify, slugifyForGit, suggestFilesLikelyTouched, suggestSubtasks, textsEqual, validatePlan, worktreeConfigSchema, worktreePathFor, worktreeRelativePath, writeEditorWorkspace };
|
package/dist/index.js
CHANGED
|
@@ -415,6 +415,19 @@ function renderWorkspaceMarkdown(ws) {
|
|
|
415
415
|
lines.push("_None detected._");
|
|
416
416
|
}
|
|
417
417
|
lines.push("");
|
|
418
|
+
lines.push("## Coding rules");
|
|
419
|
+
lines.push("");
|
|
420
|
+
lines.push("> Rule files found per repo (read non-exclusively across editors). chamba");
|
|
421
|
+
lines.push("> loads their content into context at task time.");
|
|
422
|
+
lines.push("");
|
|
423
|
+
if (ws.ruleSources.length > 0) {
|
|
424
|
+
for (const r of ws.ruleSources) {
|
|
425
|
+
lines.push(`- \`${r.path}\` \u2014 ${r.editor} (${r.repo})`);
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
lines.push("_None detected._");
|
|
429
|
+
}
|
|
430
|
+
lines.push("");
|
|
418
431
|
lines.push("## Active projects");
|
|
419
432
|
lines.push("");
|
|
420
433
|
if (ws.projects.length > 0) {
|
|
@@ -904,12 +917,12 @@ var MemoryFilesystem = class {
|
|
|
904
917
|
const prefix = norm === "/" ? "/" : `${norm}/`;
|
|
905
918
|
const seen = /* @__PURE__ */ new Set();
|
|
906
919
|
const entries = [];
|
|
907
|
-
const pushChild = (full,
|
|
920
|
+
const pushChild = (full, isDir2) => {
|
|
908
921
|
if (!full.startsWith(prefix) || full === norm) return;
|
|
909
922
|
const rest = full.slice(prefix.length);
|
|
910
923
|
const slash = rest.indexOf("/");
|
|
911
924
|
const name = slash === -1 ? rest : rest.slice(0, slash);
|
|
912
|
-
const childIsDir = slash !== -1 ||
|
|
925
|
+
const childIsDir = slash !== -1 || isDir2;
|
|
913
926
|
if (name.length === 0 || seen.has(name)) return;
|
|
914
927
|
seen.add(name);
|
|
915
928
|
entries.push({ name, isDirectory: childIsDir, isFile: !childIsDir });
|
|
@@ -920,6 +933,90 @@ var MemoryFilesystem = class {
|
|
|
920
933
|
}
|
|
921
934
|
};
|
|
922
935
|
|
|
936
|
+
// src/workspace/rules.ts
|
|
937
|
+
var RULE_SOURCES = [
|
|
938
|
+
{ editor: "Cursor", path: ".cursor/rules", kind: "dir" },
|
|
939
|
+
{ editor: "Cursor", path: ".cursorrules", kind: "file" },
|
|
940
|
+
{ editor: "Claude Code", path: "CLAUDE.md", kind: "file" },
|
|
941
|
+
{ editor: "Claude Code", path: ".claude/rules", kind: "dir" },
|
|
942
|
+
{ editor: "Windsurf", path: ".windsurfrules", kind: "file" },
|
|
943
|
+
{ editor: "Windsurf", path: ".windsurf/rules", kind: "dir" },
|
|
944
|
+
{ editor: "Trae", path: ".trae/rules", kind: "dir" },
|
|
945
|
+
{ editor: "Trae", path: ".trae/project_rules.md", kind: "file" },
|
|
946
|
+
{ editor: "GitHub Copilot", path: ".github/copilot-instructions.md", kind: "file" },
|
|
947
|
+
{ editor: "Cline", path: ".clinerules", kind: "file" },
|
|
948
|
+
{ editor: "Agents", path: "AGENTS.md", kind: "file" }
|
|
949
|
+
];
|
|
950
|
+
function isRuleFile(name) {
|
|
951
|
+
const lower = name.toLowerCase();
|
|
952
|
+
return lower.endsWith(".md") || lower.endsWith(".mdc");
|
|
953
|
+
}
|
|
954
|
+
async function isDir(fs, path) {
|
|
955
|
+
try {
|
|
956
|
+
await fs.readDir(path);
|
|
957
|
+
return true;
|
|
958
|
+
} catch {
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
async function detectRuleSources(fs, workspaceRoot, repoDirs) {
|
|
963
|
+
const found = [];
|
|
964
|
+
const seen = /* @__PURE__ */ new Set();
|
|
965
|
+
const add = (repo, editor, relPath) => {
|
|
966
|
+
if (seen.has(relPath)) return;
|
|
967
|
+
seen.add(relPath);
|
|
968
|
+
found.push({ repo, editor, path: relPath });
|
|
969
|
+
};
|
|
970
|
+
for (const repo of repoDirs) {
|
|
971
|
+
const repoAbs = repo === "." ? workspaceRoot : joinPath(workspaceRoot, repo);
|
|
972
|
+
for (const conv of RULE_SOURCES) {
|
|
973
|
+
const abs = joinPath(repoAbs, conv.path);
|
|
974
|
+
const rel = repo === "." ? conv.path : joinPath(repo, conv.path);
|
|
975
|
+
if (conv.kind === "file") {
|
|
976
|
+
if (await fs.exists(abs)) add(repo, conv.editor, rel);
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (!await isDir(fs, abs)) continue;
|
|
980
|
+
let entries;
|
|
981
|
+
try {
|
|
982
|
+
entries = await fs.readDir(abs);
|
|
983
|
+
} catch {
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
for (const entry of entries) {
|
|
987
|
+
if (entry.isFile && isRuleFile(entry.name)) {
|
|
988
|
+
add(repo, conv.editor, joinPath(rel, entry.name));
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return found;
|
|
994
|
+
}
|
|
995
|
+
async function readRuleExcerpts(fs, workspaceRoot, sources, opts = {}) {
|
|
996
|
+
const maxPer = opts.maxCharsPerRule ?? 400;
|
|
997
|
+
const total = opts.totalBudget ?? 1500;
|
|
998
|
+
const out = [];
|
|
999
|
+
let used = 0;
|
|
1000
|
+
for (const source of sources) {
|
|
1001
|
+
if (used >= total) break;
|
|
1002
|
+
let text;
|
|
1003
|
+
try {
|
|
1004
|
+
text = await fs.readFile(joinPath(workspaceRoot, source.path));
|
|
1005
|
+
} catch {
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
const budget = Math.min(maxPer, total - used);
|
|
1009
|
+
const excerpt = clampExcerpt(text.trim(), budget);
|
|
1010
|
+
used += excerpt.length;
|
|
1011
|
+
out.push({ source, excerpt });
|
|
1012
|
+
}
|
|
1013
|
+
return out;
|
|
1014
|
+
}
|
|
1015
|
+
function clampExcerpt(text, maxChars) {
|
|
1016
|
+
if (text.length <= maxChars) return text;
|
|
1017
|
+
return `${text.slice(0, Math.max(0, maxChars - 1))}\u2026`;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
923
1020
|
// src/workspace/context-builder.ts
|
|
924
1021
|
var DEFAULT_MAX_TOKENS = 2e3;
|
|
925
1022
|
var NOTE_SCAN_MAX_DEPTH = 8;
|
|
@@ -955,6 +1052,9 @@ var ContextBuilder = class {
|
|
|
955
1052
|
async build(input) {
|
|
956
1053
|
const sections2 = [this.workspaceSection(input.workspace)];
|
|
957
1054
|
let relevantNotes = [];
|
|
1055
|
+
if (input.includeRules !== false && input.workspace.ruleSources.length > 0) {
|
|
1056
|
+
sections2.push(await this.codingRulesSection(input.workspace));
|
|
1057
|
+
}
|
|
958
1058
|
if (input.vaultPath) {
|
|
959
1059
|
const notes = await this.searchNotes(input.vaultPath, input.task);
|
|
960
1060
|
relevantNotes = notes.map((n) => n.path);
|
|
@@ -964,6 +1064,22 @@ var ContextBuilder = class {
|
|
|
964
1064
|
const context = clamp(sections2.join("\n\n"), maxChars);
|
|
965
1065
|
return { context, relevantNotes };
|
|
966
1066
|
}
|
|
1067
|
+
/** Each repo's coding rules (read fresh, clamped), non-exclusive across editors. */
|
|
1068
|
+
async codingRulesSection(ws) {
|
|
1069
|
+
const excerpts = await readRuleExcerpts(this.fs, ws.root, ws.ruleSources);
|
|
1070
|
+
if (excerpts.length === 0) return "## Coding rules\n\nNo readable rule files.";
|
|
1071
|
+
const lines = [
|
|
1072
|
+
"## Coding rules",
|
|
1073
|
+
"",
|
|
1074
|
+
"Follow these per-repo rules (any editor). Read the full file for details:",
|
|
1075
|
+
""
|
|
1076
|
+
];
|
|
1077
|
+
for (const { source, excerpt } of excerpts) {
|
|
1078
|
+
lines.push(`### \`${source.path}\` \u2014 ${source.editor} (${source.repo})`);
|
|
1079
|
+
lines.push("", excerpt, "");
|
|
1080
|
+
}
|
|
1081
|
+
return lines.join("\n").trimEnd();
|
|
1082
|
+
}
|
|
967
1083
|
workspaceSection(ws) {
|
|
968
1084
|
const lines = ["## Workspace context", "", ws.description.trim()];
|
|
969
1085
|
if (ws.languages.length > 0) lines.push("", `Languages: ${ws.languages.join(", ")}`);
|
|
@@ -1005,27 +1121,7 @@ var ContextBuilder = class {
|
|
|
1005
1121
|
return scored.slice(0, MAX_NOTES);
|
|
1006
1122
|
}
|
|
1007
1123
|
async collectMarkdown(root) {
|
|
1008
|
-
|
|
1009
|
-
const visit = async (dir, depth) => {
|
|
1010
|
-
if (depth > NOTE_SCAN_MAX_DEPTH) return;
|
|
1011
|
-
let entries;
|
|
1012
|
-
try {
|
|
1013
|
-
entries = await this.fs.readDir(dir);
|
|
1014
|
-
} catch {
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
for (const entry of entries) {
|
|
1018
|
-
const full = joinPath(dir, entry.name);
|
|
1019
|
-
if (entry.isDirectory) {
|
|
1020
|
-
if (SKIP_DIRS.has(entry.name)) continue;
|
|
1021
|
-
await visit(full, depth + 1);
|
|
1022
|
-
} else if (entry.name.toLowerCase().endsWith(".md")) {
|
|
1023
|
-
out.push(full);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
};
|
|
1027
|
-
await visit(root, 0);
|
|
1028
|
-
return out;
|
|
1124
|
+
return listVaultNotes(this.fs, root);
|
|
1029
1125
|
}
|
|
1030
1126
|
async tryRead(path) {
|
|
1031
1127
|
try {
|
|
@@ -1035,6 +1131,29 @@ var ContextBuilder = class {
|
|
|
1035
1131
|
}
|
|
1036
1132
|
}
|
|
1037
1133
|
};
|
|
1134
|
+
async function listVaultNotes(fs, root) {
|
|
1135
|
+
const out = [];
|
|
1136
|
+
const visit = async (dir, depth) => {
|
|
1137
|
+
if (depth > NOTE_SCAN_MAX_DEPTH) return;
|
|
1138
|
+
let entries;
|
|
1139
|
+
try {
|
|
1140
|
+
entries = await fs.readDir(dir);
|
|
1141
|
+
} catch {
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
for (const entry of entries) {
|
|
1145
|
+
const full = joinPath(dir, entry.name);
|
|
1146
|
+
if (entry.isDirectory) {
|
|
1147
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
1148
|
+
await visit(full, depth + 1);
|
|
1149
|
+
} else if (entry.name.toLowerCase().endsWith(".md")) {
|
|
1150
|
+
out.push(full);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
await visit(root, 0);
|
|
1155
|
+
return out;
|
|
1156
|
+
}
|
|
1038
1157
|
function extractKeywords(task) {
|
|
1039
1158
|
const seen = /* @__PURE__ */ new Set();
|
|
1040
1159
|
for (const raw of task.toLowerCase().split(/[^a-z0-9áéíóúñ]+/i)) {
|
|
@@ -1108,6 +1227,10 @@ function textsEqual(oldText, newText) {
|
|
|
1108
1227
|
}
|
|
1109
1228
|
|
|
1110
1229
|
// src/workspace/obsidian-detector.ts
|
|
1230
|
+
function normalizeVaultPath(path) {
|
|
1231
|
+
const trimmed = path.replace(/[/\\]+$/, "");
|
|
1232
|
+
return basename(trimmed) === ".obsidian" ? dirname(trimmed) : trimmed;
|
|
1233
|
+
}
|
|
1111
1234
|
var COUNT_MAX_DEPTH = 8;
|
|
1112
1235
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([".obsidian", ".trash", "node_modules", ".git"]);
|
|
1113
1236
|
var ObsidianDetector = class {
|
|
@@ -1117,12 +1240,9 @@ var ObsidianDetector = class {
|
|
|
1117
1240
|
fs;
|
|
1118
1241
|
async detect(opts) {
|
|
1119
1242
|
if (opts.explicitPath) {
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
path: opts.explicitPath,
|
|
1124
|
-
noteCount: await this.countNotes(opts.explicitPath)
|
|
1125
|
-
};
|
|
1243
|
+
const vault = normalizeVaultPath(opts.explicitPath);
|
|
1244
|
+
if (await this.fs.exists(vault)) {
|
|
1245
|
+
return { found: true, path: vault, noteCount: await this.countNotes(vault) };
|
|
1126
1246
|
}
|
|
1127
1247
|
return { found: false };
|
|
1128
1248
|
}
|
|
@@ -1157,6 +1277,30 @@ var ObsidianDetector = class {
|
|
|
1157
1277
|
}
|
|
1158
1278
|
};
|
|
1159
1279
|
|
|
1280
|
+
// src/worktree/git-repo-detector.ts
|
|
1281
|
+
async function hasGitDir(fs, dir) {
|
|
1282
|
+
try {
|
|
1283
|
+
const entries = await fs.readDir(dir);
|
|
1284
|
+
return entries.some((e) => e.name === ".git" && e.isDirectory);
|
|
1285
|
+
} catch {
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
async function detectGitRepos(fs, workspaceRoot) {
|
|
1290
|
+
let entries;
|
|
1291
|
+
try {
|
|
1292
|
+
entries = await fs.readDir(workspaceRoot);
|
|
1293
|
+
} catch {
|
|
1294
|
+
return [];
|
|
1295
|
+
}
|
|
1296
|
+
const repos = [];
|
|
1297
|
+
for (const entry of entries) {
|
|
1298
|
+
if (!entry.isDirectory) continue;
|
|
1299
|
+
if (await hasGitDir(fs, joinPath(workspaceRoot, entry.name))) repos.push(entry.name);
|
|
1300
|
+
}
|
|
1301
|
+
return repos.sort();
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1160
1304
|
// src/workspace/scanner.ts
|
|
1161
1305
|
var MAX_DEPTH = 6;
|
|
1162
1306
|
var DEFAULT_IGNORES = [
|
|
@@ -1226,6 +1370,10 @@ var WorkspaceScanner = class {
|
|
|
1226
1370
|
const framework = rootProject?.framework ?? projects.find((p) => p.framework)?.framework;
|
|
1227
1371
|
const conventions = await this.detectConventions(root);
|
|
1228
1372
|
const description = await this.detectDescription(root, rootProject, framework, languages);
|
|
1373
|
+
const repoDirs = [
|
|
1374
|
+
.../* @__PURE__ */ new Set([".", ...await detectGitRepos(this.fs, root), ...projects.map((p) => p.path)])
|
|
1375
|
+
];
|
|
1376
|
+
const ruleSources = await detectRuleSources(this.fs, root, repoDirs);
|
|
1229
1377
|
return {
|
|
1230
1378
|
root,
|
|
1231
1379
|
description,
|
|
@@ -1233,6 +1381,7 @@ var WorkspaceScanner = class {
|
|
|
1233
1381
|
framework,
|
|
1234
1382
|
conventions,
|
|
1235
1383
|
projects,
|
|
1384
|
+
ruleSources,
|
|
1236
1385
|
folderMap: [...acc.topDirs].sort()
|
|
1237
1386
|
};
|
|
1238
1387
|
}
|
|
@@ -1392,10 +1541,10 @@ function globToRegExp(glob) {
|
|
|
1392
1541
|
}
|
|
1393
1542
|
return out;
|
|
1394
1543
|
}
|
|
1395
|
-
function isIgnored(rules, relPath,
|
|
1544
|
+
function isIgnored(rules, relPath, isDir2) {
|
|
1396
1545
|
const base = basename(relPath);
|
|
1397
1546
|
for (const rule of rules) {
|
|
1398
|
-
if (rule.dirOnly && !
|
|
1547
|
+
if (rule.dirOnly && !isDir2) continue;
|
|
1399
1548
|
const target = rule.anchored ? relPath : base;
|
|
1400
1549
|
if (rule.re.test(target)) return true;
|
|
1401
1550
|
}
|
|
@@ -1559,30 +1708,6 @@ var GitDetector = class {
|
|
|
1559
1708
|
}
|
|
1560
1709
|
};
|
|
1561
1710
|
|
|
1562
|
-
// src/worktree/git-repo-detector.ts
|
|
1563
|
-
async function hasGitDir(fs, dir) {
|
|
1564
|
-
try {
|
|
1565
|
-
const entries = await fs.readDir(dir);
|
|
1566
|
-
return entries.some((e) => e.name === ".git" && e.isDirectory);
|
|
1567
|
-
} catch {
|
|
1568
|
-
return false;
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
async function detectGitRepos(fs, workspaceRoot) {
|
|
1572
|
-
let entries;
|
|
1573
|
-
try {
|
|
1574
|
-
entries = await fs.readDir(workspaceRoot);
|
|
1575
|
-
} catch {
|
|
1576
|
-
return [];
|
|
1577
|
-
}
|
|
1578
|
-
const repos = [];
|
|
1579
|
-
for (const entry of entries) {
|
|
1580
|
-
if (!entry.isDirectory) continue;
|
|
1581
|
-
if (await hasGitDir(fs, joinPath(workspaceRoot, entry.name))) repos.push(entry.name);
|
|
1582
|
-
}
|
|
1583
|
-
return repos.sort();
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
1711
|
// src/worktree/manager.ts
|
|
1587
1712
|
var WorktreeError = class extends Error {
|
|
1588
1713
|
name = "WorktreeError";
|
|
@@ -1785,6 +1910,7 @@ export {
|
|
|
1785
1910
|
ObsidianDetector,
|
|
1786
1911
|
REASONING_PRIORITIES,
|
|
1787
1912
|
ROLE_DESCRIPTIONS,
|
|
1913
|
+
RULE_SOURCES,
|
|
1788
1914
|
Reviewer,
|
|
1789
1915
|
VAULT_NOTES_DIR,
|
|
1790
1916
|
VaultWriter,
|
|
@@ -1801,6 +1927,7 @@ export {
|
|
|
1801
1927
|
configFileSchema,
|
|
1802
1928
|
copyEnvFiles,
|
|
1803
1929
|
detectGitRepos,
|
|
1930
|
+
detectRuleSources,
|
|
1804
1931
|
diffLines,
|
|
1805
1932
|
dirname,
|
|
1806
1933
|
editorWorkspaceContent,
|
|
@@ -1809,10 +1936,13 @@ export {
|
|
|
1809
1936
|
generatePlanTemplate,
|
|
1810
1937
|
getModel,
|
|
1811
1938
|
joinPath,
|
|
1939
|
+
listVaultNotes,
|
|
1812
1940
|
loadConfig,
|
|
1813
1941
|
modelsByProvider,
|
|
1942
|
+
normalizeVaultPath,
|
|
1814
1943
|
parseChambaConfig,
|
|
1815
1944
|
planWorktrees,
|
|
1945
|
+
readRuleExcerpts,
|
|
1816
1946
|
renderNote,
|
|
1817
1947
|
renderWorkspaceMarkdown,
|
|
1818
1948
|
resolveEffort,
|
package/package.json
CHANGED