@ilya-lesikov/pi-pi 0.1.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/3p/pi-ask-user/index.ts +1835 -0
- package/3p/pi-ask-user/package.json +50 -0
- package/3p/pi-ask-user/single-select-layout.ts +203 -0
- package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
- package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
- package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
- package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
- package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
- package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
- package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
- package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
- package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
- package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
- package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
- package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
- package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
- package/3p/pi-lsp/package.json +54 -0
- package/3p/pi-plannotator/AGENTS.md +583 -0
- package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
- package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
- package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
- package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
- package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
- package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
- package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
- package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
- package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
- package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
- package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
- package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
- package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
- package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
- package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
- package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
- package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
- package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
- package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
- package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
- package/3p/pi-plannotator/package.json +53 -0
- package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
- package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
- package/3p/pi-plannotator/packages/ai/context.ts +250 -0
- package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
- package/3p/pi-plannotator/packages/ai/index.ts +105 -0
- package/3p/pi-plannotator/packages/ai/package.json +21 -0
- package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
- package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
- package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
- package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
- package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
- package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
- package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/ai/types.ts +379 -0
- package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
- package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
- package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
- package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
- package/3p/pi-plannotator/packages/server/browser.ts +245 -0
- package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
- package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
- package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
- package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
- package/3p/pi-plannotator/packages/server/config.ts +8 -0
- package/3p/pi-plannotator/packages/server/draft.ts +1 -0
- package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
- package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
- package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
- package/3p/pi-plannotator/packages/server/git.ts +141 -0
- package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
- package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
- package/3p/pi-plannotator/packages/server/ide.ts +43 -0
- package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
- package/3p/pi-plannotator/packages/server/image.ts +66 -0
- package/3p/pi-plannotator/packages/server/index.ts +648 -0
- package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
- package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
- package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
- package/3p/pi-plannotator/packages/server/jj.ts +86 -0
- package/3p/pi-plannotator/packages/server/p4.ts +417 -0
- package/3p/pi-plannotator/packages/server/package.json +40 -0
- package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
- package/3p/pi-plannotator/packages/server/pr.ts +146 -0
- package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
- package/3p/pi-plannotator/packages/server/project.ts +45 -0
- package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
- package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
- package/3p/pi-plannotator/packages/server/remote.ts +74 -0
- package/3p/pi-plannotator/packages/server/repo.ts +80 -0
- package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
- package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
- package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
- package/3p/pi-plannotator/packages/server/review.ts +1318 -0
- package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
- package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
- package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
- package/3p/pi-plannotator/packages/server/storage.ts +17 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
- package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
- package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
- package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
- package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
- package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
- package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
- package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
- package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
- package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
- package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
- package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
- package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
- package/3p/pi-plannotator/packages/shared/config.ts +262 -0
- package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
- package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
- package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
- package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
- package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
- package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
- package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
- package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
- package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
- package/3p/pi-plannotator/packages/shared/package.json +55 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
- package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
- package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
- package/3p/pi-plannotator/packages/shared/project.ts +71 -0
- package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
- package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
- package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
- package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
- package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
- package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
- package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
- package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
- package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
- package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
- package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
- package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
- package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
- package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/shared/types.ts +29 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
- package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
- package/3p/pi-subagents/package.json +50 -0
- package/3p/pi-subagents/src/agent-manager.ts +413 -0
- package/3p/pi-subagents/src/agent-runner.ts +502 -0
- package/3p/pi-subagents/src/agent-types.ts +248 -0
- package/3p/pi-subagents/src/context.ts +58 -0
- package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
- package/3p/pi-subagents/src/custom-agents.ts +137 -0
- package/3p/pi-subagents/src/default-agents.ts +144 -0
- package/3p/pi-subagents/src/env.ts +33 -0
- package/3p/pi-subagents/src/group-join.ts +141 -0
- package/3p/pi-subagents/src/index.ts +1811 -0
- package/3p/pi-subagents/src/invocation-config.ts +40 -0
- package/3p/pi-subagents/src/memory.ts +165 -0
- package/3p/pi-subagents/src/model-resolver.ts +81 -0
- package/3p/pi-subagents/src/output-file.ts +77 -0
- package/3p/pi-subagents/src/prompts.ts +85 -0
- package/3p/pi-subagents/src/skill-loader.ts +79 -0
- package/3p/pi-subagents/src/types.ts +111 -0
- package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
- package/3p/pi-subagents/src/worktree.ts +162 -0
- package/3p/pi-tasks/package.json +52 -0
- package/3p/pi-tasks/src/auto-clear.ts +91 -0
- package/3p/pi-tasks/src/index.ts +1150 -0
- package/3p/pi-tasks/src/process-tracker.ts +140 -0
- package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
- package/3p/pi-tasks/src/task-store.ts +324 -0
- package/3p/pi-tasks/src/tasks-config.ts +27 -0
- package/3p/pi-tasks/src/types.ts +40 -0
- package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
- package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
- package/AGENTS.md +28 -0
- package/LICENSE +201 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
- package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
- package/extensions/orchestrator/agents/explore.ts +33 -0
- package/extensions/orchestrator/agents/librarian.ts +43 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
- package/extensions/orchestrator/agents/planner.ts +61 -0
- package/extensions/orchestrator/agents/registry.ts +156 -0
- package/extensions/orchestrator/agents/task.ts +50 -0
- package/extensions/orchestrator/agents/tool-routing.ts +84 -0
- package/extensions/orchestrator/ast-search.ts +85 -0
- package/extensions/orchestrator/cbm.ts +330 -0
- package/extensions/orchestrator/command-handlers.test.ts +163 -0
- package/extensions/orchestrator/command-handlers.ts +116 -0
- package/extensions/orchestrator/commands.test.ts +81 -0
- package/extensions/orchestrator/commands.ts +75 -0
- package/extensions/orchestrator/config.test.ts +221 -0
- package/extensions/orchestrator/config.ts +230 -0
- package/extensions/orchestrator/context.test.ts +293 -0
- package/extensions/orchestrator/context.ts +206 -0
- package/extensions/orchestrator/event-handlers.test.ts +190 -0
- package/extensions/orchestrator/event-handlers.ts +1416 -0
- package/extensions/orchestrator/exa.ts +104 -0
- package/extensions/orchestrator/flant-infra.ts +486 -0
- package/extensions/orchestrator/index.ts +80 -0
- package/extensions/orchestrator/integration.test.ts +1214 -0
- package/extensions/orchestrator/orchestrator.test.ts +252 -0
- package/extensions/orchestrator/orchestrator.ts +538 -0
- package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
- package/extensions/orchestrator/phases/brainstorm.ts +273 -0
- package/extensions/orchestrator/phases/implementation.ts +40 -0
- package/extensions/orchestrator/phases/machine.test.ts +293 -0
- package/extensions/orchestrator/phases/machine.ts +209 -0
- package/extensions/orchestrator/phases/planning.ts +255 -0
- package/extensions/orchestrator/phases/review.ts +193 -0
- package/extensions/orchestrator/plannotator.ts +56 -0
- package/extensions/orchestrator/pp-menu.ts +866 -0
- package/extensions/orchestrator/state.test.ts +343 -0
- package/extensions/orchestrator/state.ts +237 -0
- package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
- package/extensions/orchestrator/validate-artifacts.ts +272 -0
- package/extensions/orchestrator/vendor.d.ts +26 -0
- package/package.json +73 -0
- package/scripts/postinstall.sh +18 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
// --- Types ---
|
|
5
|
+
|
|
6
|
+
export interface ObsidianConfig {
|
|
7
|
+
vaultPath: string;
|
|
8
|
+
folder: string;
|
|
9
|
+
plan: string;
|
|
10
|
+
filenameFormat?: string; // Custom format string, e.g. '{YYYY}-{MM}-{DD} - {title}'
|
|
11
|
+
filenameSeparator?: "space" | "dash" | "underscore"; // Replace spaces in filename
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface BearConfig {
|
|
15
|
+
plan: string;
|
|
16
|
+
customTags?: string;
|
|
17
|
+
tagPosition?: "prepend" | "append";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OctarineConfig {
|
|
21
|
+
plan: string;
|
|
22
|
+
workspace: string;
|
|
23
|
+
folder: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface IntegrationResult {
|
|
27
|
+
success: boolean;
|
|
28
|
+
error?: string;
|
|
29
|
+
path?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Detect Obsidian vaults by reading Obsidian's config file
|
|
34
|
+
* Returns array of vault paths found on the system
|
|
35
|
+
*/
|
|
36
|
+
export function detectObsidianVaults(): string[] {
|
|
37
|
+
try {
|
|
38
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
39
|
+
let configPath: string;
|
|
40
|
+
|
|
41
|
+
// Platform-specific config locations
|
|
42
|
+
if (process.platform === "darwin") {
|
|
43
|
+
configPath = join(
|
|
44
|
+
home,
|
|
45
|
+
"Library/Application Support/obsidian/obsidian.json",
|
|
46
|
+
);
|
|
47
|
+
} else if (process.platform === "win32") {
|
|
48
|
+
const appData = process.env.APPDATA || join(home, "AppData/Roaming");
|
|
49
|
+
configPath = join(appData, "obsidian/obsidian.json");
|
|
50
|
+
} else {
|
|
51
|
+
// Linux
|
|
52
|
+
configPath = join(home, ".config/obsidian/obsidian.json");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!existsSync(configPath)) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const configContent = readFileSync(configPath, "utf-8");
|
|
60
|
+
const config = JSON.parse(configContent);
|
|
61
|
+
|
|
62
|
+
if (!config.vaults || typeof config.vaults !== "object") {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Extract vault paths, filter to ones that exist
|
|
67
|
+
const vaults: string[] = [];
|
|
68
|
+
for (const vaultId of Object.keys(config.vaults)) {
|
|
69
|
+
const vault = config.vaults[vaultId];
|
|
70
|
+
if (vault.path && existsSync(vault.path)) {
|
|
71
|
+
vaults.push(vault.path);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return vaults;
|
|
76
|
+
} catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Frontmatter and Filename Generation ---
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generate frontmatter for the note
|
|
85
|
+
*/
|
|
86
|
+
export function generateFrontmatter(tags: string[]): string {
|
|
87
|
+
const now = new Date().toISOString();
|
|
88
|
+
const tagList = tags.map((t) => t.toLowerCase()).join(", ");
|
|
89
|
+
return `---
|
|
90
|
+
created: ${now}
|
|
91
|
+
source: plannotator
|
|
92
|
+
tags: [${tagList}]
|
|
93
|
+
---`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Extract title from markdown (first H1 heading)
|
|
98
|
+
*/
|
|
99
|
+
export function extractTitle(markdown: string): string {
|
|
100
|
+
const h1Match = markdown.match(
|
|
101
|
+
/^#\s+(?:Implementation\s+Plan:|Plan:)?\s*(.+)$/im,
|
|
102
|
+
);
|
|
103
|
+
if (h1Match) {
|
|
104
|
+
// Clean up the title for use as filename
|
|
105
|
+
return h1Match[1]
|
|
106
|
+
.trim()
|
|
107
|
+
.replace(/[<>:"/\\|?*(){}\[\]#~`]/g, "") // Remove invalid/problematic filename chars
|
|
108
|
+
.replace(/\s+/g, " ") // Normalize whitespace
|
|
109
|
+
.trim() // Re-trim after stripping
|
|
110
|
+
.slice(0, 50); // Limit length
|
|
111
|
+
}
|
|
112
|
+
return "Plan";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Default filename format matching original behavior */
|
|
116
|
+
export const DEFAULT_FILENAME_FORMAT =
|
|
117
|
+
"{title} - {Mon} {D}, {YYYY} {h}-{mm}{ampm}";
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Generate filename from a format string with variable substitution.
|
|
121
|
+
*
|
|
122
|
+
* Supported variables:
|
|
123
|
+
* {title} - Plan title from first H1 heading
|
|
124
|
+
* {YYYY} - 4-digit year
|
|
125
|
+
* {MM} - 2-digit month (01-12)
|
|
126
|
+
* {DD} - 2-digit day (01-31)
|
|
127
|
+
* {Mon} - Abbreviated month name (Jan, Feb, ...)
|
|
128
|
+
* {D} - Day without leading zero
|
|
129
|
+
* {HH} - 2-digit hour, 24h (00-23)
|
|
130
|
+
* {h} - Hour without leading zero, 12h
|
|
131
|
+
* {hh} - 2-digit hour, 12h (01-12)
|
|
132
|
+
* {mm} - 2-digit minutes (00-59)
|
|
133
|
+
* {ss} - 2-digit seconds (00-59)
|
|
134
|
+
* {ampm} - am/pm
|
|
135
|
+
*
|
|
136
|
+
* Default format: '{title} - {Mon} {D}, {YYYY} {h}-{mm}{ampm}'
|
|
137
|
+
* Example output: 'User Authentication - Jan 2, 2026 2-30pm.md'
|
|
138
|
+
*/
|
|
139
|
+
export function generateFilename(
|
|
140
|
+
markdown: string,
|
|
141
|
+
format?: string,
|
|
142
|
+
separator?: "space" | "dash" | "underscore",
|
|
143
|
+
): string {
|
|
144
|
+
const title = extractTitle(markdown);
|
|
145
|
+
const now = new Date();
|
|
146
|
+
|
|
147
|
+
const months = [
|
|
148
|
+
"Jan",
|
|
149
|
+
"Feb",
|
|
150
|
+
"Mar",
|
|
151
|
+
"Apr",
|
|
152
|
+
"May",
|
|
153
|
+
"Jun",
|
|
154
|
+
"Jul",
|
|
155
|
+
"Aug",
|
|
156
|
+
"Sep",
|
|
157
|
+
"Oct",
|
|
158
|
+
"Nov",
|
|
159
|
+
"Dec",
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const hour24 = now.getHours();
|
|
163
|
+
const hour12 = hour24 % 12 || 12;
|
|
164
|
+
const ampm = hour24 >= 12 ? "pm" : "am";
|
|
165
|
+
|
|
166
|
+
const vars: Record<string, string> = {
|
|
167
|
+
title,
|
|
168
|
+
YYYY: String(now.getFullYear()),
|
|
169
|
+
MM: String(now.getMonth() + 1).padStart(2, "0"),
|
|
170
|
+
DD: String(now.getDate()).padStart(2, "0"),
|
|
171
|
+
Mon: months[now.getMonth()],
|
|
172
|
+
D: String(now.getDate()),
|
|
173
|
+
HH: String(hour24).padStart(2, "0"),
|
|
174
|
+
h: String(hour12),
|
|
175
|
+
hh: String(hour12).padStart(2, "0"),
|
|
176
|
+
mm: String(now.getMinutes()).padStart(2, "0"),
|
|
177
|
+
ss: String(now.getSeconds()).padStart(2, "0"),
|
|
178
|
+
ampm,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const template = format?.trim() || DEFAULT_FILENAME_FORMAT;
|
|
182
|
+
const result = template.replace(
|
|
183
|
+
/\{(\w+)\}/g,
|
|
184
|
+
(match, key) => vars[key] ?? match,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Sanitize: remove characters invalid in filenames
|
|
188
|
+
let sanitized = result
|
|
189
|
+
.replace(/[<>:"/\\|?*]/g, "")
|
|
190
|
+
.replace(/\s+/g, " ")
|
|
191
|
+
.trim();
|
|
192
|
+
|
|
193
|
+
// Apply separator preference (replace spaces with dash or underscore)
|
|
194
|
+
if (separator === "dash") {
|
|
195
|
+
sanitized = sanitized.replace(/ /g, "-");
|
|
196
|
+
} else if (separator === "underscore") {
|
|
197
|
+
sanitized = sanitized.replace(/ /g, "_");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return sanitized.endsWith(".md") ? sanitized : `${sanitized}.md`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// --- Bear Integration ---
|
|
204
|
+
|
|
205
|
+
export function stripH1(plan: string): string {
|
|
206
|
+
return plan.replace(/^#\s+.+\n?/m, "").trimStart();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function buildHashtags(
|
|
210
|
+
customTags: string | undefined,
|
|
211
|
+
autoTags: string[],
|
|
212
|
+
): string {
|
|
213
|
+
if (customTags?.trim()) {
|
|
214
|
+
return customTags
|
|
215
|
+
.split(",")
|
|
216
|
+
.map((t) => `#${t.trim()}`)
|
|
217
|
+
.filter((t) => t !== "#")
|
|
218
|
+
.join(" ");
|
|
219
|
+
}
|
|
220
|
+
return autoTags.map((t) => `#${t}`).join(" ");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function buildBearContent(
|
|
224
|
+
body: string,
|
|
225
|
+
hashtags: string,
|
|
226
|
+
tagPosition: "prepend" | "append",
|
|
227
|
+
): string {
|
|
228
|
+
return tagPosition === "prepend"
|
|
229
|
+
? `${hashtags}\n\n${body}`
|
|
230
|
+
: `${body}\n\n${hashtags}`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// --- Octarine Integration ---
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Generate YAML frontmatter for an Octarine note.
|
|
237
|
+
* Uses Octarine's property format (list-style tags, Status, Author, Last Edited).
|
|
238
|
+
*/
|
|
239
|
+
export function generateOctarineFrontmatter(tags: string[]): string {
|
|
240
|
+
const now = new Date().toISOString().slice(0, 16); // YYYY-MM-DDTHH:MM
|
|
241
|
+
const tagLines = tags.map((t) => ` - ${t.toLowerCase()}`).join("\n");
|
|
242
|
+
return `---\ntags:\n${tagLines}\nStatus: Draft\nAuthor: plannotator\nLast Edited: ${now}\n---`;
|
|
243
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
getJjDiffArgs,
|
|
4
|
+
getJjEvoLogEntries,
|
|
5
|
+
jjLineBaseRevset,
|
|
6
|
+
parseJjBookmarkList,
|
|
7
|
+
parseJjRemoteBookmarkList,
|
|
8
|
+
type ReviewJjRuntime,
|
|
9
|
+
runJjDiff,
|
|
10
|
+
selectDefaultJjCompareTarget,
|
|
11
|
+
} from "./jj-core";
|
|
12
|
+
|
|
13
|
+
describe("jj diff args", () => {
|
|
14
|
+
test("builds git-format diff args for each jj mode", () => {
|
|
15
|
+
expect(getJjDiffArgs("jj-current", "trunk()")).toEqual({
|
|
16
|
+
args: ["diff", "--git", "-r", "@"],
|
|
17
|
+
label: "Current change",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(getJjDiffArgs("jj-last", "trunk()")).toEqual({
|
|
21
|
+
args: ["diff", "--git", "-r", "@-"],
|
|
22
|
+
label: "Last change",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(getJjDiffArgs("jj-line", "trunk()")).toEqual({
|
|
26
|
+
args: ["diff", "--git", "--from", "heads(::@ & ::(trunk()))", "--to", "@"],
|
|
27
|
+
label: "Line of work vs trunk()",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(getJjDiffArgs("jj-all", "trunk()")).toEqual({
|
|
31
|
+
args: ["diff", "--git", "--from", "root()", "--to", "@"],
|
|
32
|
+
label: "All files",
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("preserves hide-whitespace in every jj diff mode", () => {
|
|
37
|
+
expect(getJjDiffArgs("jj-current", "trunk()", { hideWhitespace: true })?.args)
|
|
38
|
+
.toEqual(["diff", "--git", "-w", "-r", "@"]);
|
|
39
|
+
expect(getJjDiffArgs("jj-last", "trunk()", { hideWhitespace: true })?.args)
|
|
40
|
+
.toEqual(["diff", "--git", "-w", "-r", "@-"]);
|
|
41
|
+
expect(getJjDiffArgs("jj-line", "trunk()", { hideWhitespace: true })?.args)
|
|
42
|
+
.toEqual(["diff", "--git", "-w", "--from", "heads(::@ & ::(trunk()))", "--to", "@"]);
|
|
43
|
+
expect(getJjDiffArgs("jj-all", "trunk()", { hideWhitespace: true })?.args)
|
|
44
|
+
.toEqual(["diff", "--git", "-w", "--from", "root()", "--to", "@"]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("drops hunk-less file chunks after hide-whitespace filtering", async () => {
|
|
48
|
+
const runtimeForPatch = (stdout: string): ReviewJjRuntime => ({
|
|
49
|
+
async runJj() {
|
|
50
|
+
return {
|
|
51
|
+
stdout,
|
|
52
|
+
stderr: "",
|
|
53
|
+
exitCode: 0,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const hunklessChunk = [
|
|
59
|
+
"diff --git a/spacey.ts b/spacey.ts",
|
|
60
|
+
"index 1111111..2222222 100644",
|
|
61
|
+
"--- a/spacey.ts",
|
|
62
|
+
"+++ b/spacey.ts",
|
|
63
|
+
"",
|
|
64
|
+
].join("\n");
|
|
65
|
+
const realChunk = [
|
|
66
|
+
"diff --git a/real.ts b/real.ts",
|
|
67
|
+
"index 3333333..4444444 100644",
|
|
68
|
+
"--- a/real.ts",
|
|
69
|
+
"+++ b/real.ts",
|
|
70
|
+
"@@ -1 +1 @@",
|
|
71
|
+
"-old",
|
|
72
|
+
"+new",
|
|
73
|
+
"",
|
|
74
|
+
].join("\n");
|
|
75
|
+
|
|
76
|
+
const result = await runJjDiff(runtimeForPatch(hunklessChunk + realChunk), "jj-current", "trunk()", undefined, { hideWhitespace: true });
|
|
77
|
+
|
|
78
|
+
expect(result.patch).not.toContain("spacey.ts");
|
|
79
|
+
expect(result.patch).toContain("real.ts");
|
|
80
|
+
expect(result.patch).toContain("@@ -1 +1 @@");
|
|
81
|
+
|
|
82
|
+
const emptyResult = await runJjDiff(runtimeForPatch(hunklessChunk), "jj-current", "trunk()", undefined, { hideWhitespace: true });
|
|
83
|
+
expect(emptyResult.patch).toBe("");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("jj compare targets", () => {
|
|
88
|
+
test("resolves default target from jj trunk remote bookmarks", async () => {
|
|
89
|
+
const calls: string[][] = [];
|
|
90
|
+
const runtime: ReviewJjRuntime = {
|
|
91
|
+
async runJj(args) {
|
|
92
|
+
calls.push(args);
|
|
93
|
+
return { stdout: '[{"name":"main"},{"name":"main","remote":"origin"}]\n', stderr: "", exitCode: 0 };
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
await expect(selectDefaultJjCompareTarget(runtime, "/repo"))
|
|
98
|
+
.resolves.toBe("main@origin");
|
|
99
|
+
expect(calls).toEqual([[
|
|
100
|
+
"log",
|
|
101
|
+
"--no-graph",
|
|
102
|
+
"-r",
|
|
103
|
+
"trunk()",
|
|
104
|
+
"-T",
|
|
105
|
+
"json(bookmarks)",
|
|
106
|
+
]]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("falls back to local bookmark then trunk revset", async () => {
|
|
110
|
+
const runtimeFor = (stdout: string): ReviewJjRuntime => ({
|
|
111
|
+
async runJj() {
|
|
112
|
+
return { stdout, stderr: "", exitCode: 0 };
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await expect(selectDefaultJjCompareTarget(runtimeFor('[{"name":"develop"}]\n')))
|
|
117
|
+
.resolves.toBe("develop");
|
|
118
|
+
await expect(selectDefaultJjCompareTarget(runtimeFor('[]\n')))
|
|
119
|
+
.resolves.toBe("trunk()");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("treats bookmarks and revsets correctly in line-of-work revsets", () => {
|
|
123
|
+
expect(jjLineBaseRevset("main")).toBe('heads(::@ & ::(bookmarks(exact:"main")))');
|
|
124
|
+
expect(jjLineBaseRevset("main@origin")).toBe('heads(::@ & ::(remote_bookmarks(exact:"main", exact:"origin")))');
|
|
125
|
+
expect(jjLineBaseRevset("trunk()")).toBe("heads(::@ & ::(trunk()))");
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("jj evolog", () => {
|
|
130
|
+
test("builds evolog diff args with explicit base", () => {
|
|
131
|
+
expect(getJjDiffArgs("jj-evolog", "abc123456789")).toEqual({
|
|
132
|
+
args: ["diff", "--git", "--from", "abc123456789", "--to", "@"],
|
|
133
|
+
label: "Evolution diff from abc12345",
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("builds evolog diff args with whitespace flag", () => {
|
|
138
|
+
expect(getJjDiffArgs("jj-evolog", "abc123456789", { hideWhitespace: true })?.args)
|
|
139
|
+
.toEqual(["diff", "--git", "-w", "--from", "abc123456789", "--to", "@"]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("parses evolog output correctly (commit.* template fields)", async () => {
|
|
143
|
+
const runtime: ReviewJjRuntime = {
|
|
144
|
+
async runJj() {
|
|
145
|
+
return {
|
|
146
|
+
stdout: [
|
|
147
|
+
"abc123456789\tAdd login form\t2 minutes ago",
|
|
148
|
+
"def456789012\tAdd login form\t10 minutes ago",
|
|
149
|
+
"ghi789012345\tAdd login form\t1 hour ago",
|
|
150
|
+
"",
|
|
151
|
+
].join("\n"),
|
|
152
|
+
stderr: "",
|
|
153
|
+
exitCode: 0,
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
const entries = await getJjEvoLogEntries(runtime);
|
|
158
|
+
expect(entries).toHaveLength(3);
|
|
159
|
+
expect(entries[0]).toEqual({ commitId: "abc123456789", description: "Add login form", age: "2 minutes ago" });
|
|
160
|
+
expect(entries[1]).toEqual({ commitId: "def456789012", description: "Add login form", age: "10 minutes ago" });
|
|
161
|
+
expect(entries[2]).toEqual({ commitId: "ghi789012345", description: "Add login form", age: "1 hour ago" });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("returns empty array when evolog exits non-zero", async () => {
|
|
165
|
+
const runtime: ReviewJjRuntime = {
|
|
166
|
+
async runJj() {
|
|
167
|
+
return { stdout: "", stderr: "error: no such revision", exitCode: 1 };
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
const entries = await getJjEvoLogEntries(runtime);
|
|
171
|
+
expect(entries).toHaveLength(0);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("defaults to second evolog entry when no base given", async () => {
|
|
175
|
+
let callCount = 0;
|
|
176
|
+
const calls: string[][] = [];
|
|
177
|
+
const runtime: ReviewJjRuntime = {
|
|
178
|
+
async runJj(args) {
|
|
179
|
+
calls.push(args);
|
|
180
|
+
callCount++;
|
|
181
|
+
if (args[0] === "evolog") {
|
|
182
|
+
return {
|
|
183
|
+
stdout: [
|
|
184
|
+
"abc123456789\tFix bug\t1 minute ago",
|
|
185
|
+
"def456789012\tFix bug\t5 minutes ago",
|
|
186
|
+
"",
|
|
187
|
+
].join("\n"),
|
|
188
|
+
stderr: "",
|
|
189
|
+
exitCode: 0,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return { stdout: "diff --git a/foo.ts b/foo.ts\n", stderr: "", exitCode: 0 };
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
const result = await runJjDiff(runtime, "jj-evolog", "");
|
|
196
|
+
expect(result.label).toBe("Evolution diff from def45678");
|
|
197
|
+
// evolog call + diff call
|
|
198
|
+
expect(callCount).toBe(2);
|
|
199
|
+
const diffCall = calls.find((c) => c[0] === "diff");
|
|
200
|
+
expect(diffCall).toEqual(["diff", "--git", "--from", "def456789012", "--to", "@"]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("returns error when evolog has no previous entry", async () => {
|
|
204
|
+
const runtime: ReviewJjRuntime = {
|
|
205
|
+
async runJj(args) {
|
|
206
|
+
if (args[0] === "evolog") {
|
|
207
|
+
return { stdout: "abc123456789\tInitial commit\t1 minute ago\n", stderr: "", exitCode: 0 };
|
|
208
|
+
}
|
|
209
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
const result = await runJjDiff(runtime, "jj-evolog", "");
|
|
213
|
+
expect(result.error).toBe("No previous evolution found");
|
|
214
|
+
expect(result.patch).toBe("");
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("jj bookmark parsing", () => {
|
|
219
|
+
test("parses escaped newline separators from jj bookmark templates", () => {
|
|
220
|
+
expect(parseJjBookmarkList('"dev"\\n"main"\\n')).toEqual(["dev", "main"]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("parses escaped tab and newline separators from jj remote bookmark templates", () => {
|
|
224
|
+
expect(parseJjRemoteBookmarkList('"main"\\t"git"\\n"release"\\t"origin"\\n')).toEqual([
|
|
225
|
+
"main@git",
|
|
226
|
+
"release@origin",
|
|
227
|
+
]);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("preserves git remote bookmarks from colocated jj repositories", () => {
|
|
231
|
+
expect(parseJjRemoteBookmarkList('"main"\t"git"\n"release"\t"origin"\n')).toEqual([
|
|
232
|
+
"main@git",
|
|
233
|
+
"release@origin",
|
|
234
|
+
]);
|
|
235
|
+
});
|
|
236
|
+
});
|