claude-code-pilot 2.0.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/LICENSE +21 -0
- package/README.md +151 -0
- package/bin/install.js +431 -0
- package/docs/agent-guides/architecture.md +107 -0
- package/ecc/agents/architect.md +211 -0
- package/ecc/agents/code-reviewer.md +237 -0
- package/ecc/agents/doc-updater.md +107 -0
- package/ecc/agents/e2e-runner.md +107 -0
- package/ecc/agents/security-reviewer.md +108 -0
- package/ecc/agents/tdd-guide.md +91 -0
- package/ecc/commands/checkpoint.md +74 -0
- package/ecc/commands/evolve.md +178 -0
- package/ecc/commands/learn.md +70 -0
- package/ecc/commands/model-route.md +26 -0
- package/ecc/commands/quality-gate.md +29 -0
- package/ecc/commands/resume-session.md +155 -0
- package/ecc/commands/save-session.md +275 -0
- package/ecc/commands/sessions.md +305 -0
- package/ecc/commands/verify.md +59 -0
- package/ecc/contexts/dev.md +20 -0
- package/ecc/contexts/research.md +26 -0
- package/ecc/contexts/review.md +22 -0
- package/ecc/examples/CLAUDE.md +100 -0
- package/ecc/examples/django-api-CLAUDE.md +308 -0
- package/ecc/examples/go-microservice-CLAUDE.md +267 -0
- package/ecc/examples/rust-api-CLAUDE.md +285 -0
- package/ecc/examples/saas-nextjs-CLAUDE.md +166 -0
- package/ecc/examples/user-CLAUDE.md +109 -0
- package/ecc/rules/common/agents.md +49 -0
- package/ecc/rules/common/coding-style.md +48 -0
- package/ecc/rules/common/development-workflow.md +37 -0
- package/ecc/rules/common/git-workflow.md +24 -0
- package/ecc/rules/common/hooks.md +30 -0
- package/ecc/rules/common/patterns.md +31 -0
- package/ecc/rules/common/performance.md +55 -0
- package/ecc/rules/common/security.md +29 -0
- package/ecc/rules/common/testing.md +29 -0
- package/ecc/rules/golang/coding-style.md +32 -0
- package/ecc/rules/golang/hooks.md +17 -0
- package/ecc/rules/golang/patterns.md +45 -0
- package/ecc/rules/golang/security.md +34 -0
- package/ecc/rules/golang/testing.md +31 -0
- package/ecc/rules/kotlin/coding-style.md +86 -0
- package/ecc/rules/kotlin/patterns.md +146 -0
- package/ecc/rules/kotlin/security.md +82 -0
- package/ecc/rules/kotlin/testing.md +128 -0
- package/ecc/rules/perl/coding-style.md +46 -0
- package/ecc/rules/perl/hooks.md +22 -0
- package/ecc/rules/perl/patterns.md +76 -0
- package/ecc/rules/perl/security.md +69 -0
- package/ecc/rules/perl/testing.md +54 -0
- package/ecc/rules/php/coding-style.md +35 -0
- package/ecc/rules/php/hooks.md +24 -0
- package/ecc/rules/php/patterns.md +32 -0
- package/ecc/rules/php/security.md +33 -0
- package/ecc/rules/php/testing.md +34 -0
- package/ecc/rules/python/coding-style.md +42 -0
- package/ecc/rules/python/hooks.md +19 -0
- package/ecc/rules/python/patterns.md +39 -0
- package/ecc/rules/python/security.md +30 -0
- package/ecc/rules/python/testing.md +38 -0
- package/ecc/rules/swift/coding-style.md +47 -0
- package/ecc/rules/swift/hooks.md +20 -0
- package/ecc/rules/swift/patterns.md +66 -0
- package/ecc/rules/swift/security.md +33 -0
- package/ecc/rules/swift/testing.md +45 -0
- package/ecc/rules/typescript/coding-style.md +199 -0
- package/ecc/rules/typescript/hooks.md +22 -0
- package/ecc/rules/typescript/patterns.md +52 -0
- package/ecc/rules/typescript/security.md +28 -0
- package/ecc/rules/typescript/testing.md +18 -0
- package/ecc/scripts/hooks/check-hook-enabled.js +12 -0
- package/ecc/scripts/hooks/evaluate-session.js +100 -0
- package/ecc/scripts/hooks/pre-compact.js +48 -0
- package/ecc/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/ecc/scripts/hooks/run-with-flags.js +120 -0
- package/ecc/scripts/hooks/session-end-marker.js +15 -0
- package/ecc/scripts/hooks/session-end.js +258 -0
- package/ecc/scripts/hooks/session-start.js +97 -0
- package/ecc/scripts/hooks/suggest-compact.js +80 -0
- package/ecc/scripts/lib/hook-flags.js +74 -0
- package/ecc/scripts/lib/package-manager.d.ts +119 -0
- package/ecc/scripts/lib/package-manager.js +431 -0
- package/ecc/scripts/lib/project-detect.js +428 -0
- package/ecc/scripts/lib/resolve-formatter.js +185 -0
- package/ecc/scripts/lib/session-aliases.d.ts +136 -0
- package/ecc/scripts/lib/session-aliases.js +481 -0
- package/ecc/scripts/lib/session-manager.d.ts +131 -0
- package/ecc/scripts/lib/session-manager.js +444 -0
- package/ecc/scripts/lib/shell-split.js +86 -0
- package/ecc/scripts/lib/utils.d.ts +183 -0
- package/ecc/scripts/lib/utils.js +543 -0
- package/ecc/skills/continuous-learning-v2/SKILL.md +365 -0
- package/ecc/skills/continuous-learning-v2/agents/observer-loop.sh +144 -0
- package/ecc/skills/continuous-learning-v2/agents/observer.md +198 -0
- package/ecc/skills/continuous-learning-v2/agents/start-observer.sh +194 -0
- package/ecc/skills/continuous-learning-v2/config.json +8 -0
- package/ecc/skills/continuous-learning-v2/hooks/observe.sh +246 -0
- package/ecc/skills/continuous-learning-v2/scripts/detect-project.sh +218 -0
- package/ecc/skills/continuous-learning-v2/scripts/instinct-cli.py +1148 -0
- package/ecc/skills/continuous-learning-v2/scripts/test_parse_instinct.py +984 -0
- package/ecc/skills/strategic-compact/SKILL.md +103 -0
- package/ecc/skills/strategic-compact/suggest-compact.sh +54 -0
- package/ecc/skills/verification-loop-SKILL.md +126 -0
- package/gsd/LICENSE +21 -0
- package/gsd/agents/gsd-codebase-mapper.md +772 -0
- package/gsd/agents/gsd-debugger.md +1257 -0
- package/gsd/agents/gsd-executor.md +489 -0
- package/gsd/agents/gsd-integration-checker.md +445 -0
- package/gsd/agents/gsd-nyquist-auditor.md +178 -0
- package/gsd/agents/gsd-phase-researcher.md +555 -0
- package/gsd/agents/gsd-plan-checker.md +708 -0
- package/gsd/agents/gsd-planner.md +1309 -0
- package/gsd/agents/gsd-project-researcher.md +631 -0
- package/gsd/agents/gsd-research-synthesizer.md +249 -0
- package/gsd/agents/gsd-roadmapper.md +652 -0
- package/gsd/agents/gsd-verifier.md +581 -0
- package/gsd/commands-gsd/add-phase.md +43 -0
- package/gsd/commands-gsd/add-tests.md +41 -0
- package/gsd/commands-gsd/add-todo.md +47 -0
- package/gsd/commands-gsd/audit-milestone.md +36 -0
- package/gsd/commands-gsd/check-todos.md +45 -0
- package/gsd/commands-gsd/cleanup.md +18 -0
- package/gsd/commands-gsd/complete-milestone.md +136 -0
- package/gsd/commands-gsd/debug.md +168 -0
- package/gsd/commands-gsd/discuss-phase.md +90 -0
- package/gsd/commands-gsd/execute-phase.md +41 -0
- package/gsd/commands-gsd/health.md +22 -0
- package/gsd/commands-gsd/help.md +22 -0
- package/gsd/commands-gsd/insert-phase.md +32 -0
- package/gsd/commands-gsd/join-discord.md +18 -0
- package/gsd/commands-gsd/list-phase-assumptions.md +46 -0
- package/gsd/commands-gsd/map-codebase.md +71 -0
- package/gsd/commands-gsd/new-milestone.md +44 -0
- package/gsd/commands-gsd/new-project.md +42 -0
- package/gsd/commands-gsd/pause-work.md +38 -0
- package/gsd/commands-gsd/plan-milestone-gaps.md +34 -0
- package/gsd/commands-gsd/plan-phase.md +45 -0
- package/gsd/commands-gsd/progress.md +24 -0
- package/gsd/commands-gsd/quick.md +45 -0
- package/gsd/commands-gsd/reapply-patches.md +123 -0
- package/gsd/commands-gsd/remove-phase.md +31 -0
- package/gsd/commands-gsd/research-phase.md +190 -0
- package/gsd/commands-gsd/resume-work.md +40 -0
- package/gsd/commands-gsd/set-profile.md +34 -0
- package/gsd/commands-gsd/settings.md +36 -0
- package/gsd/commands-gsd/update.md +37 -0
- package/gsd/commands-gsd/validate-phase.md +35 -0
- package/gsd/commands-gsd/verify-work.md +38 -0
- package/gsd/get-shit-done/bin/gsd-tools.cjs +592 -0
- package/gsd/get-shit-done/bin/lib/commands.cjs +548 -0
- package/gsd/get-shit-done/bin/lib/config.cjs +169 -0
- package/gsd/get-shit-done/bin/lib/core.cjs +492 -0
- package/gsd/get-shit-done/bin/lib/frontmatter.cjs +299 -0
- package/gsd/get-shit-done/bin/lib/init.cjs +710 -0
- package/gsd/get-shit-done/bin/lib/milestone.cjs +241 -0
- package/gsd/get-shit-done/bin/lib/phase.cjs +901 -0
- package/gsd/get-shit-done/bin/lib/roadmap.cjs +298 -0
- package/gsd/get-shit-done/bin/lib/state.cjs +721 -0
- package/gsd/get-shit-done/bin/lib/template.cjs +222 -0
- package/gsd/get-shit-done/bin/lib/verify.cjs +820 -0
- package/gsd/get-shit-done/references/checkpoints.md +776 -0
- package/gsd/get-shit-done/references/continuation-format.md +249 -0
- package/gsd/get-shit-done/references/decimal-phase-calculation.md +65 -0
- package/gsd/get-shit-done/references/git-integration.md +248 -0
- package/gsd/get-shit-done/references/git-planning-commit.md +38 -0
- package/gsd/get-shit-done/references/model-profile-resolution.md +34 -0
- package/gsd/get-shit-done/references/model-profiles.md +93 -0
- package/gsd/get-shit-done/references/phase-argument-parsing.md +61 -0
- package/gsd/get-shit-done/references/planning-config.md +200 -0
- package/gsd/get-shit-done/references/questioning.md +162 -0
- package/gsd/get-shit-done/references/tdd.md +263 -0
- package/gsd/get-shit-done/references/ui-brand.md +160 -0
- package/gsd/get-shit-done/references/verification-patterns.md +612 -0
- package/gsd/get-shit-done/templates/DEBUG.md +164 -0
- package/gsd/get-shit-done/templates/UAT.md +247 -0
- package/gsd/get-shit-done/templates/VALIDATION.md +76 -0
- package/gsd/get-shit-done/templates/codebase/architecture.md +255 -0
- package/gsd/get-shit-done/templates/codebase/concerns.md +310 -0
- package/gsd/get-shit-done/templates/codebase/conventions.md +307 -0
- package/gsd/get-shit-done/templates/codebase/integrations.md +280 -0
- package/gsd/get-shit-done/templates/codebase/stack.md +186 -0
- package/gsd/get-shit-done/templates/codebase/structure.md +285 -0
- package/gsd/get-shit-done/templates/codebase/testing.md +480 -0
- package/gsd/get-shit-done/templates/config.json +37 -0
- package/gsd/get-shit-done/templates/context.md +297 -0
- package/gsd/get-shit-done/templates/continue-here.md +78 -0
- package/gsd/get-shit-done/templates/debug-subagent-prompt.md +91 -0
- package/gsd/get-shit-done/templates/discovery.md +146 -0
- package/gsd/get-shit-done/templates/milestone-archive.md +123 -0
- package/gsd/get-shit-done/templates/milestone.md +115 -0
- package/gsd/get-shit-done/templates/phase-prompt.md +569 -0
- package/gsd/get-shit-done/templates/planner-subagent-prompt.md +117 -0
- package/gsd/get-shit-done/templates/project.md +184 -0
- package/gsd/get-shit-done/templates/requirements.md +231 -0
- package/gsd/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
- package/gsd/get-shit-done/templates/research-project/FEATURES.md +147 -0
- package/gsd/get-shit-done/templates/research-project/PITFALLS.md +200 -0
- package/gsd/get-shit-done/templates/research-project/STACK.md +120 -0
- package/gsd/get-shit-done/templates/research-project/SUMMARY.md +170 -0
- package/gsd/get-shit-done/templates/research.md +552 -0
- package/gsd/get-shit-done/templates/retrospective.md +54 -0
- package/gsd/get-shit-done/templates/roadmap.md +202 -0
- package/gsd/get-shit-done/templates/state.md +176 -0
- package/gsd/get-shit-done/templates/summary-complex.md +59 -0
- package/gsd/get-shit-done/templates/summary-minimal.md +41 -0
- package/gsd/get-shit-done/templates/summary-standard.md +48 -0
- package/gsd/get-shit-done/templates/summary.md +248 -0
- package/gsd/get-shit-done/templates/user-setup.md +311 -0
- package/gsd/get-shit-done/templates/verification-report.md +322 -0
- package/gsd/get-shit-done/workflows/add-phase.md +112 -0
- package/gsd/get-shit-done/workflows/add-tests.md +351 -0
- package/gsd/get-shit-done/workflows/add-todo.md +158 -0
- package/gsd/get-shit-done/workflows/audit-milestone.md +332 -0
- package/gsd/get-shit-done/workflows/check-todos.md +177 -0
- package/gsd/get-shit-done/workflows/cleanup.md +152 -0
- package/gsd/get-shit-done/workflows/complete-milestone.md +764 -0
- package/gsd/get-shit-done/workflows/diagnose-issues.md +219 -0
- package/gsd/get-shit-done/workflows/discovery-phase.md +289 -0
- package/gsd/get-shit-done/workflows/discuss-phase.md +676 -0
- package/gsd/get-shit-done/workflows/execute-phase.md +459 -0
- package/gsd/get-shit-done/workflows/execute-plan.md +449 -0
- package/gsd/get-shit-done/workflows/health.md +159 -0
- package/gsd/get-shit-done/workflows/help.md +489 -0
- package/gsd/get-shit-done/workflows/insert-phase.md +130 -0
- package/gsd/get-shit-done/workflows/list-phase-assumptions.md +178 -0
- package/gsd/get-shit-done/workflows/map-codebase.md +316 -0
- package/gsd/get-shit-done/workflows/new-milestone.md +384 -0
- package/gsd/get-shit-done/workflows/new-project.md +1111 -0
- package/gsd/get-shit-done/workflows/pause-work.md +122 -0
- package/gsd/get-shit-done/workflows/plan-milestone-gaps.md +274 -0
- package/gsd/get-shit-done/workflows/plan-phase.md +560 -0
- package/gsd/get-shit-done/workflows/progress.md +382 -0
- package/gsd/get-shit-done/workflows/quick.md +601 -0
- package/gsd/get-shit-done/workflows/remove-phase.md +155 -0
- package/gsd/get-shit-done/workflows/research-phase.md +74 -0
- package/gsd/get-shit-done/workflows/resume-project.md +307 -0
- package/gsd/get-shit-done/workflows/set-profile.md +81 -0
- package/gsd/get-shit-done/workflows/settings.md +214 -0
- package/gsd/get-shit-done/workflows/transition.md +544 -0
- package/gsd/get-shit-done/workflows/update.md +240 -0
- package/gsd/get-shit-done/workflows/validate-phase.md +167 -0
- package/gsd/get-shit-done/workflows/verify-phase.md +243 -0
- package/gsd/get-shit-done/workflows/verify-work.md +583 -0
- package/gsd/hooks/gsd-check-update.js +81 -0
- package/gsd/hooks/gsd-context-monitor.js +141 -0
- package/gsd/hooks/gsd-statusline.js +115 -0
- package/kit/CLAUDE.md +43 -0
- package/kit/commands/kit/update.md +46 -0
- package/kit/commands/setup-refresh.md +50 -0
- package/kit/commands/setup.md +579 -0
- package/kit/commands/tool-guide.md +44 -0
- package/kit/hooks/kit-check-update.js +54 -0
- package/kit/mcp.json +10 -0
- package/kit/rules/code-style.md +24 -0
- package/manifest.json +30 -0
- package/package.json +36 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Aliases Library for Claude Code.
|
|
3
|
+
* Manages named aliases for session files, stored in ~/.claude/session-aliases.json.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Internal alias storage entry */
|
|
7
|
+
export interface AliasEntry {
|
|
8
|
+
sessionPath: string;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
updatedAt?: string;
|
|
11
|
+
title: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Alias data structure stored on disk */
|
|
15
|
+
export interface AliasStore {
|
|
16
|
+
version: string;
|
|
17
|
+
aliases: Record<string, AliasEntry>;
|
|
18
|
+
metadata: {
|
|
19
|
+
totalCount: number;
|
|
20
|
+
lastUpdated: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Resolved alias information returned by resolveAlias */
|
|
25
|
+
export interface ResolvedAlias {
|
|
26
|
+
alias: string;
|
|
27
|
+
sessionPath: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
title: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Alias entry returned by listAliases */
|
|
33
|
+
export interface AliasListItem {
|
|
34
|
+
name: string;
|
|
35
|
+
sessionPath: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
updatedAt?: string;
|
|
38
|
+
title: string | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Result from mutation operations (set, delete, rename, update, cleanup) */
|
|
42
|
+
export interface AliasResult {
|
|
43
|
+
success: boolean;
|
|
44
|
+
error?: string;
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SetAliasResult extends AliasResult {
|
|
49
|
+
isNew?: boolean;
|
|
50
|
+
alias?: string;
|
|
51
|
+
sessionPath?: string;
|
|
52
|
+
title?: string | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface DeleteAliasResult extends AliasResult {
|
|
56
|
+
alias?: string;
|
|
57
|
+
deletedSessionPath?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RenameAliasResult extends AliasResult {
|
|
61
|
+
oldAlias?: string;
|
|
62
|
+
newAlias?: string;
|
|
63
|
+
sessionPath?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CleanupResult {
|
|
67
|
+
totalChecked: number;
|
|
68
|
+
removed: number;
|
|
69
|
+
removedAliases: Array<{ name: string; sessionPath: string }>;
|
|
70
|
+
error?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ListAliasesOptions {
|
|
74
|
+
/** Filter aliases by name or title (partial match, case-insensitive) */
|
|
75
|
+
search?: string | null;
|
|
76
|
+
/** Maximum number of aliases to return */
|
|
77
|
+
limit?: number | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Get the path to the aliases JSON file */
|
|
81
|
+
export function getAliasesPath(): string;
|
|
82
|
+
|
|
83
|
+
/** Load all aliases from disk. Returns default structure if file doesn't exist. */
|
|
84
|
+
export function loadAliases(): AliasStore;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Save aliases to disk with atomic write (temp file + rename).
|
|
88
|
+
* Creates backup before writing; restores on failure.
|
|
89
|
+
*/
|
|
90
|
+
export function saveAliases(aliases: AliasStore): boolean;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resolve an alias name to its session data.
|
|
94
|
+
* @returns Alias data, or null if not found or invalid name
|
|
95
|
+
*/
|
|
96
|
+
export function resolveAlias(alias: string): ResolvedAlias | null;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create or update an alias for a session.
|
|
100
|
+
* Alias names must be alphanumeric with dashes/underscores.
|
|
101
|
+
* Reserved names (list, help, remove, delete, create, set) are rejected.
|
|
102
|
+
*/
|
|
103
|
+
export function setAlias(alias: string, sessionPath: string, title?: string | null): SetAliasResult;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* List all aliases, optionally filtered and limited.
|
|
107
|
+
* Results are sorted by updated time (newest first).
|
|
108
|
+
*/
|
|
109
|
+
export function listAliases(options?: ListAliasesOptions): AliasListItem[];
|
|
110
|
+
|
|
111
|
+
/** Delete an alias by name */
|
|
112
|
+
export function deleteAlias(alias: string): DeleteAliasResult;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Rename an alias. Fails if old alias doesn't exist or new alias already exists.
|
|
116
|
+
* New alias name must be alphanumeric with dashes/underscores.
|
|
117
|
+
*/
|
|
118
|
+
export function renameAlias(oldAlias: string, newAlias: string): RenameAliasResult;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolve an alias or pass through a session path.
|
|
122
|
+
* First tries to resolve as alias; if not found, returns the input as-is.
|
|
123
|
+
*/
|
|
124
|
+
export function resolveSessionAlias(aliasOrId: string): string;
|
|
125
|
+
|
|
126
|
+
/** Update the title of an existing alias. Pass null to clear. */
|
|
127
|
+
export function updateAliasTitle(alias: string, title: string | null): AliasResult;
|
|
128
|
+
|
|
129
|
+
/** Get all aliases that point to a specific session path */
|
|
130
|
+
export function getAliasesForSession(sessionPath: string): Array<{ name: string; createdAt: string; title: string | null }>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Remove aliases whose sessions no longer exist.
|
|
134
|
+
* @param sessionExists - Function that returns true if a session path is valid
|
|
135
|
+
*/
|
|
136
|
+
export function cleanupAliases(sessionExists: (sessionPath: string) => boolean): CleanupResult;
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Aliases Library for Claude Code
|
|
3
|
+
* Manages session aliases stored in ~/.claude/session-aliases.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
getClaudeDir,
|
|
11
|
+
ensureDir,
|
|
12
|
+
readFile,
|
|
13
|
+
log
|
|
14
|
+
} = require('./utils');
|
|
15
|
+
|
|
16
|
+
// Aliases file path
|
|
17
|
+
function getAliasesPath() {
|
|
18
|
+
return path.join(getClaudeDir(), 'session-aliases.json');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Current alias storage format version
|
|
22
|
+
const ALIAS_VERSION = '1.0';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default aliases file structure
|
|
26
|
+
*/
|
|
27
|
+
function getDefaultAliases() {
|
|
28
|
+
return {
|
|
29
|
+
version: ALIAS_VERSION,
|
|
30
|
+
aliases: {},
|
|
31
|
+
metadata: {
|
|
32
|
+
totalCount: 0,
|
|
33
|
+
lastUpdated: new Date().toISOString()
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load aliases from file
|
|
40
|
+
* @returns {object} Aliases object
|
|
41
|
+
*/
|
|
42
|
+
function loadAliases() {
|
|
43
|
+
const aliasesPath = getAliasesPath();
|
|
44
|
+
|
|
45
|
+
if (!fs.existsSync(aliasesPath)) {
|
|
46
|
+
return getDefaultAliases();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const content = readFile(aliasesPath);
|
|
50
|
+
if (!content) {
|
|
51
|
+
return getDefaultAliases();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const data = JSON.parse(content);
|
|
56
|
+
|
|
57
|
+
// Validate structure
|
|
58
|
+
if (!data.aliases || typeof data.aliases !== 'object') {
|
|
59
|
+
log('[Aliases] Invalid aliases file structure, resetting');
|
|
60
|
+
return getDefaultAliases();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Ensure version field
|
|
64
|
+
if (!data.version) {
|
|
65
|
+
data.version = ALIAS_VERSION;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Ensure metadata
|
|
69
|
+
if (!data.metadata) {
|
|
70
|
+
data.metadata = {
|
|
71
|
+
totalCount: Object.keys(data.aliases).length,
|
|
72
|
+
lastUpdated: new Date().toISOString()
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return data;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
log(`[Aliases] Error parsing aliases file: ${err.message}`);
|
|
79
|
+
return getDefaultAliases();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Save aliases to file with atomic write
|
|
85
|
+
* @param {object} aliases - Aliases object to save
|
|
86
|
+
* @returns {boolean} Success status
|
|
87
|
+
*/
|
|
88
|
+
function saveAliases(aliases) {
|
|
89
|
+
const aliasesPath = getAliasesPath();
|
|
90
|
+
const tempPath = aliasesPath + '.tmp';
|
|
91
|
+
const backupPath = aliasesPath + '.bak';
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// Update metadata
|
|
95
|
+
aliases.metadata = {
|
|
96
|
+
totalCount: Object.keys(aliases.aliases).length,
|
|
97
|
+
lastUpdated: new Date().toISOString()
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const content = JSON.stringify(aliases, null, 2);
|
|
101
|
+
|
|
102
|
+
// Ensure directory exists
|
|
103
|
+
ensureDir(path.dirname(aliasesPath));
|
|
104
|
+
|
|
105
|
+
// Create backup if file exists
|
|
106
|
+
if (fs.existsSync(aliasesPath)) {
|
|
107
|
+
fs.copyFileSync(aliasesPath, backupPath);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Atomic write: write to temp file, then rename
|
|
111
|
+
fs.writeFileSync(tempPath, content, 'utf8');
|
|
112
|
+
|
|
113
|
+
// On Windows, rename fails with EEXIST if destination exists, so delete first.
|
|
114
|
+
// On Unix/macOS, rename(2) atomically replaces the destination — skip the
|
|
115
|
+
// delete to avoid an unnecessary non-atomic window between unlink and rename.
|
|
116
|
+
if (process.platform === 'win32' && fs.existsSync(aliasesPath)) {
|
|
117
|
+
fs.unlinkSync(aliasesPath);
|
|
118
|
+
}
|
|
119
|
+
fs.renameSync(tempPath, aliasesPath);
|
|
120
|
+
|
|
121
|
+
// Remove backup on success
|
|
122
|
+
if (fs.existsSync(backupPath)) {
|
|
123
|
+
fs.unlinkSync(backupPath);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return true;
|
|
127
|
+
} catch (err) {
|
|
128
|
+
log(`[Aliases] Error saving aliases: ${err.message}`);
|
|
129
|
+
|
|
130
|
+
// Restore from backup if exists
|
|
131
|
+
if (fs.existsSync(backupPath)) {
|
|
132
|
+
try {
|
|
133
|
+
fs.copyFileSync(backupPath, aliasesPath);
|
|
134
|
+
log('[Aliases] Restored from backup');
|
|
135
|
+
} catch (restoreErr) {
|
|
136
|
+
log(`[Aliases] Failed to restore backup: ${restoreErr.message}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Clean up temp file (best-effort)
|
|
141
|
+
try {
|
|
142
|
+
if (fs.existsSync(tempPath)) {
|
|
143
|
+
fs.unlinkSync(tempPath);
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
// Non-critical: temp file will be overwritten on next save
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Resolve an alias to get session path
|
|
155
|
+
* @param {string} alias - Alias name to resolve
|
|
156
|
+
* @returns {object|null} Alias data or null if not found
|
|
157
|
+
*/
|
|
158
|
+
function resolveAlias(alias) {
|
|
159
|
+
if (!alias) return null;
|
|
160
|
+
|
|
161
|
+
// Validate alias name (alphanumeric, dash, underscore)
|
|
162
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(alias)) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const data = loadAliases();
|
|
167
|
+
const aliasData = data.aliases[alias];
|
|
168
|
+
|
|
169
|
+
if (!aliasData) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
alias,
|
|
175
|
+
sessionPath: aliasData.sessionPath,
|
|
176
|
+
createdAt: aliasData.createdAt,
|
|
177
|
+
title: aliasData.title || null
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Set or update an alias for a session
|
|
183
|
+
* @param {string} alias - Alias name (alphanumeric, dash, underscore)
|
|
184
|
+
* @param {string} sessionPath - Session directory path
|
|
185
|
+
* @param {string} title - Optional title for the alias
|
|
186
|
+
* @returns {object} Result with success status and message
|
|
187
|
+
*/
|
|
188
|
+
function setAlias(alias, sessionPath, title = null) {
|
|
189
|
+
// Validate alias name
|
|
190
|
+
if (!alias || alias.length === 0) {
|
|
191
|
+
return { success: false, error: 'Alias name cannot be empty' };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Validate session path
|
|
195
|
+
if (!sessionPath || typeof sessionPath !== 'string' || sessionPath.trim().length === 0) {
|
|
196
|
+
return { success: false, error: 'Session path cannot be empty' };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (alias.length > 128) {
|
|
200
|
+
return { success: false, error: 'Alias name cannot exceed 128 characters' };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(alias)) {
|
|
204
|
+
return { success: false, error: 'Alias name must contain only letters, numbers, dashes, and underscores' };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Reserved alias names
|
|
208
|
+
const reserved = ['list', 'help', 'remove', 'delete', 'create', 'set'];
|
|
209
|
+
if (reserved.includes(alias.toLowerCase())) {
|
|
210
|
+
return { success: false, error: `'${alias}' is a reserved alias name` };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const data = loadAliases();
|
|
214
|
+
const existing = data.aliases[alias];
|
|
215
|
+
const isNew = !existing;
|
|
216
|
+
|
|
217
|
+
data.aliases[alias] = {
|
|
218
|
+
sessionPath,
|
|
219
|
+
createdAt: existing ? existing.createdAt : new Date().toISOString(),
|
|
220
|
+
updatedAt: new Date().toISOString(),
|
|
221
|
+
title: title || null
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (saveAliases(data)) {
|
|
225
|
+
return {
|
|
226
|
+
success: true,
|
|
227
|
+
isNew,
|
|
228
|
+
alias,
|
|
229
|
+
sessionPath,
|
|
230
|
+
title: data.aliases[alias].title
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return { success: false, error: 'Failed to save alias' };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* List all aliases
|
|
239
|
+
* @param {object} options - Options object
|
|
240
|
+
* @param {string} options.search - Filter aliases by name (partial match)
|
|
241
|
+
* @param {number} options.limit - Maximum number of aliases to return
|
|
242
|
+
* @returns {Array} Array of alias objects
|
|
243
|
+
*/
|
|
244
|
+
function listAliases(options = {}) {
|
|
245
|
+
const { search = null, limit = null } = options;
|
|
246
|
+
const data = loadAliases();
|
|
247
|
+
|
|
248
|
+
let aliases = Object.entries(data.aliases).map(([name, info]) => ({
|
|
249
|
+
name,
|
|
250
|
+
sessionPath: info.sessionPath,
|
|
251
|
+
createdAt: info.createdAt,
|
|
252
|
+
updatedAt: info.updatedAt,
|
|
253
|
+
title: info.title
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
// Sort by updated time (newest first)
|
|
257
|
+
aliases.sort((a, b) => (new Date(b.updatedAt || b.createdAt || 0).getTime() || 0) - (new Date(a.updatedAt || a.createdAt || 0).getTime() || 0));
|
|
258
|
+
|
|
259
|
+
// Apply search filter
|
|
260
|
+
if (search) {
|
|
261
|
+
const searchLower = search.toLowerCase();
|
|
262
|
+
aliases = aliases.filter(a =>
|
|
263
|
+
a.name.toLowerCase().includes(searchLower) ||
|
|
264
|
+
(a.title && a.title.toLowerCase().includes(searchLower))
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Apply limit
|
|
269
|
+
if (limit && limit > 0) {
|
|
270
|
+
aliases = aliases.slice(0, limit);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return aliases;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Delete an alias
|
|
278
|
+
* @param {string} alias - Alias name to delete
|
|
279
|
+
* @returns {object} Result with success status
|
|
280
|
+
*/
|
|
281
|
+
function deleteAlias(alias) {
|
|
282
|
+
const data = loadAliases();
|
|
283
|
+
|
|
284
|
+
if (!data.aliases[alias]) {
|
|
285
|
+
return { success: false, error: `Alias '${alias}' not found` };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const deleted = data.aliases[alias];
|
|
289
|
+
delete data.aliases[alias];
|
|
290
|
+
|
|
291
|
+
if (saveAliases(data)) {
|
|
292
|
+
return {
|
|
293
|
+
success: true,
|
|
294
|
+
alias,
|
|
295
|
+
deletedSessionPath: deleted.sessionPath
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { success: false, error: 'Failed to delete alias' };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Rename an alias
|
|
304
|
+
* @param {string} oldAlias - Current alias name
|
|
305
|
+
* @param {string} newAlias - New alias name
|
|
306
|
+
* @returns {object} Result with success status
|
|
307
|
+
*/
|
|
308
|
+
function renameAlias(oldAlias, newAlias) {
|
|
309
|
+
const data = loadAliases();
|
|
310
|
+
|
|
311
|
+
if (!data.aliases[oldAlias]) {
|
|
312
|
+
return { success: false, error: `Alias '${oldAlias}' not found` };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Validate new alias name (same rules as setAlias)
|
|
316
|
+
if (!newAlias || newAlias.length === 0) {
|
|
317
|
+
return { success: false, error: 'New alias name cannot be empty' };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (newAlias.length > 128) {
|
|
321
|
+
return { success: false, error: 'New alias name cannot exceed 128 characters' };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(newAlias)) {
|
|
325
|
+
return { success: false, error: 'New alias name must contain only letters, numbers, dashes, and underscores' };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const reserved = ['list', 'help', 'remove', 'delete', 'create', 'set'];
|
|
329
|
+
if (reserved.includes(newAlias.toLowerCase())) {
|
|
330
|
+
return { success: false, error: `'${newAlias}' is a reserved alias name` };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (data.aliases[newAlias]) {
|
|
334
|
+
return { success: false, error: `Alias '${newAlias}' already exists` };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const aliasData = data.aliases[oldAlias];
|
|
338
|
+
delete data.aliases[oldAlias];
|
|
339
|
+
|
|
340
|
+
aliasData.updatedAt = new Date().toISOString();
|
|
341
|
+
data.aliases[newAlias] = aliasData;
|
|
342
|
+
|
|
343
|
+
if (saveAliases(data)) {
|
|
344
|
+
return {
|
|
345
|
+
success: true,
|
|
346
|
+
oldAlias,
|
|
347
|
+
newAlias,
|
|
348
|
+
sessionPath: aliasData.sessionPath
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Restore old alias and remove new alias on failure
|
|
353
|
+
data.aliases[oldAlias] = aliasData;
|
|
354
|
+
delete data.aliases[newAlias];
|
|
355
|
+
// Attempt to persist the rollback
|
|
356
|
+
saveAliases(data);
|
|
357
|
+
return { success: false, error: 'Failed to save renamed alias — rolled back to original' };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get session path by alias (convenience function)
|
|
362
|
+
* @param {string} aliasOrId - Alias name or session ID
|
|
363
|
+
* @returns {string|null} Session path or null if not found
|
|
364
|
+
*/
|
|
365
|
+
function resolveSessionAlias(aliasOrId) {
|
|
366
|
+
// First try to resolve as alias
|
|
367
|
+
const resolved = resolveAlias(aliasOrId);
|
|
368
|
+
if (resolved) {
|
|
369
|
+
return resolved.sessionPath;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// If not an alias, return as-is (might be a session path)
|
|
373
|
+
return aliasOrId;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Update alias title
|
|
378
|
+
* @param {string} alias - Alias name
|
|
379
|
+
* @param {string|null} title - New title (string or null to clear)
|
|
380
|
+
* @returns {object} Result with success status
|
|
381
|
+
*/
|
|
382
|
+
function updateAliasTitle(alias, title) {
|
|
383
|
+
if (title !== null && typeof title !== 'string') {
|
|
384
|
+
return { success: false, error: 'Title must be a string or null' };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const data = loadAliases();
|
|
388
|
+
|
|
389
|
+
if (!data.aliases[alias]) {
|
|
390
|
+
return { success: false, error: `Alias '${alias}' not found` };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
data.aliases[alias].title = title || null;
|
|
394
|
+
data.aliases[alias].updatedAt = new Date().toISOString();
|
|
395
|
+
|
|
396
|
+
if (saveAliases(data)) {
|
|
397
|
+
return {
|
|
398
|
+
success: true,
|
|
399
|
+
alias,
|
|
400
|
+
title
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return { success: false, error: 'Failed to update alias title' };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Get all aliases for a specific session
|
|
409
|
+
* @param {string} sessionPath - Session path to find aliases for
|
|
410
|
+
* @returns {Array} Array of alias names
|
|
411
|
+
*/
|
|
412
|
+
function getAliasesForSession(sessionPath) {
|
|
413
|
+
const data = loadAliases();
|
|
414
|
+
const aliases = [];
|
|
415
|
+
|
|
416
|
+
for (const [name, info] of Object.entries(data.aliases)) {
|
|
417
|
+
if (info.sessionPath === sessionPath) {
|
|
418
|
+
aliases.push({
|
|
419
|
+
name,
|
|
420
|
+
createdAt: info.createdAt,
|
|
421
|
+
title: info.title
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return aliases;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Clean up aliases for non-existent sessions
|
|
431
|
+
* @param {Function} sessionExists - Function to check if session exists
|
|
432
|
+
* @returns {object} Cleanup result
|
|
433
|
+
*/
|
|
434
|
+
function cleanupAliases(sessionExists) {
|
|
435
|
+
if (typeof sessionExists !== 'function') {
|
|
436
|
+
return { totalChecked: 0, removed: 0, removedAliases: [], error: 'sessionExists must be a function' };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const data = loadAliases();
|
|
440
|
+
const removed = [];
|
|
441
|
+
|
|
442
|
+
for (const [name, info] of Object.entries(data.aliases)) {
|
|
443
|
+
if (!sessionExists(info.sessionPath)) {
|
|
444
|
+
removed.push({ name, sessionPath: info.sessionPath });
|
|
445
|
+
delete data.aliases[name];
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (removed.length > 0 && !saveAliases(data)) {
|
|
450
|
+
log('[Aliases] Failed to save after cleanup');
|
|
451
|
+
return {
|
|
452
|
+
success: false,
|
|
453
|
+
totalChecked: Object.keys(data.aliases).length + removed.length,
|
|
454
|
+
removed: removed.length,
|
|
455
|
+
removedAliases: removed,
|
|
456
|
+
error: 'Failed to save after cleanup'
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
success: true,
|
|
462
|
+
totalChecked: Object.keys(data.aliases).length + removed.length,
|
|
463
|
+
removed: removed.length,
|
|
464
|
+
removedAliases: removed
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
module.exports = {
|
|
469
|
+
getAliasesPath,
|
|
470
|
+
loadAliases,
|
|
471
|
+
saveAliases,
|
|
472
|
+
resolveAlias,
|
|
473
|
+
setAlias,
|
|
474
|
+
listAliases,
|
|
475
|
+
deleteAlias,
|
|
476
|
+
renameAlias,
|
|
477
|
+
resolveSessionAlias,
|
|
478
|
+
updateAliasTitle,
|
|
479
|
+
getAliasesForSession,
|
|
480
|
+
cleanupAliases
|
|
481
|
+
};
|