@tsuzuku/arise 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ module.exports = {
5
+ name: 'generic',
6
+
7
+ detect(cwd) {
8
+ return true; // Fallback preset
9
+ },
10
+
11
+ repo: {
12
+ defaultBaseBranch: 'main',
13
+ protectedBranches: ['main', 'master', 'develop', 'prod', 'staging', 'production'],
14
+ },
15
+
16
+ workspace: {
17
+ labelPrefix: '',
18
+ defaultFocus: 'agy',
19
+ },
20
+
21
+ layout: [
22
+ { id: 'vim', title: 'vim', cmd: 'vim .', position: 'root' },
23
+ { id: 'shell', title: 'shell', cmd: null, split: 'right', from: 'vim' },
24
+ { id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'shell', focus: true, isAgent: true },
25
+ ],
26
+
27
+ hooks: {
28
+ async onScaffold(ctx) {
29
+ // Copy .env from root if available
30
+ ctx.copyFromRoot('.env', '.env');
31
+ },
32
+ },
33
+ };
@@ -0,0 +1,39 @@
1
+ const nodePreset = require('./node');
2
+ const laravelPreset = require('./laravel');
3
+ const genericPreset = require('./generic');
4
+
5
+ const builtInPresets = [
6
+ laravelPreset, // Check Laravel/PHP before generic
7
+ nodePreset, // Check Node before generic
8
+ genericPreset, // Catch-all fallback
9
+ ];
10
+
11
+ function getPreset(name) {
12
+ if (!name) return null;
13
+ const normalized = name.toLowerCase().trim();
14
+ if (normalized === 'php' || normalized === 'laravel' || normalized === 'api' || normalized === 'be') {
15
+ return laravelPreset;
16
+ }
17
+ if (normalized === 'node' || normalized === 'js' || normalized === 'ts' || normalized === 'fe' || normalized === 'react') {
18
+ return nodePreset;
19
+ }
20
+ if (normalized === 'generic' || normalized === 'default') {
21
+ return genericPreset;
22
+ }
23
+ return null;
24
+ }
25
+
26
+ function detectPreset(cwd = process.cwd()) {
27
+ for (const preset of builtInPresets) {
28
+ if (typeof preset.detect === 'function' && preset.detect(cwd)) {
29
+ return preset;
30
+ }
31
+ }
32
+ return genericPreset;
33
+ }
34
+
35
+ module.exports = {
36
+ getPreset,
37
+ detectPreset,
38
+ builtInPresets,
39
+ };
@@ -0,0 +1,115 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const git = require('../lib/git');
4
+
5
+ module.exports = {
6
+ name: 'laravel',
7
+
8
+ detect(cwd) {
9
+ return fs.existsSync(path.join(cwd, 'artisan')) || fs.existsSync(path.join(cwd, 'composer.json'));
10
+ },
11
+
12
+ repo: {
13
+ defaultBaseBranch: 'main',
14
+ protectedBranches: ['staging', 'prod', 'master', 'main', 'develop'],
15
+ },
16
+
17
+ workspace: {
18
+ labelPrefix: '',
19
+ defaultFocus: 'agy',
20
+ },
21
+
22
+ layout: [
23
+ { id: 'vim', title: 'vim', cmd: 'vim .', position: 'root' },
24
+ { id: 'logs', title: 'logs', cmd: 'tail -f storage/logs/laravel.log', split: 'right', from: 'vim' },
25
+ { id: 'shell', title: 'shell', cmd: null, split: 'down', from: 'vim' },
26
+ { id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'logs', focus: true, isAgent: true },
27
+ ],
28
+
29
+ hooks: {
30
+ async onSyncPrimary(ctx) {
31
+ git.syncPrimaryBranch({
32
+ branch: ctx.branch,
33
+ worktreePath: ctx.worktreePath,
34
+ repoDir: ctx.repoRoot,
35
+ bareRepo: ctx.bareRepo,
36
+ protectedBranches: ['staging', 'prod', 'master', 'main', 'develop'],
37
+ });
38
+ },
39
+
40
+ async onScaffold(ctx) {
41
+ // 1. Environment (.env) Setup
42
+ console.log('==> Ensuring .env file...');
43
+ const rootEnv = ctx.repoRoot ? path.join(ctx.repoRoot, '.env') : null;
44
+ const targetEnv = path.join(ctx.worktreePath, '.env');
45
+
46
+ if (!fs.existsSync(targetEnv)) {
47
+ if (ctx.config.scaffold && ctx.config.scaffold.envSource && fs.existsSync(ctx.config.scaffold.envSource)) {
48
+ ctx.copyFile(ctx.config.scaffold.envSource, targetEnv);
49
+ } else if (rootEnv && fs.existsSync(rootEnv)) {
50
+ ctx.copyFile(rootEnv, targetEnv);
51
+ } else {
52
+ console.warn('==> No .env source found to copy.');
53
+ }
54
+ }
55
+
56
+ // 2. Dependencies & Initialization
57
+ console.log('==> Initializing repository (permissions, composer, etc.)...');
58
+ try {
59
+ const installCmd = (ctx.config.scaffold && ctx.config.scaffold.install !== undefined)
60
+ ? ctx.config.scaffold.install
61
+ : 'composer install --no-interaction';
62
+
63
+ if (installCmd) {
64
+ console.log(`Running dependency installation: "${installCmd}" in "${ctx.worktreePath}"...`);
65
+ ctx.exec(installCmd);
66
+ } else {
67
+ console.log('Skipping composer installation (scaffold.install is disabled).');
68
+ }
69
+
70
+ const permissionsScript = `
71
+ mkdir -p storage/logs
72
+ chmod 777 storage/logs
73
+ touch storage/logs/laravel.log
74
+ chmod 666 storage/logs/laravel.log
75
+ mkdir -p storage/framework/sessions
76
+ chmod o+w storage/framework/sessions
77
+ mkdir -p storage/framework/data
78
+ chmod o+w storage/framework/data
79
+ mkdir -p storage/framework/cache/data
80
+ chmod 777 storage/framework/cache/data
81
+ mkdir -p bootstrap/cache
82
+ chmod 777 bootstrap/cache
83
+ mkdir -p storage/temp
84
+ chmod 777 storage/temp
85
+ if [ -d .githooks ]; then git config core.hooksPath .githooks; fi
86
+ `;
87
+ ctx.exec(permissionsScript);
88
+ } catch (err) {
89
+ console.warn(`==> Warning during repository initialization: ${err.message}`);
90
+ }
91
+
92
+ // 3. Update Web Server Symlink if configured
93
+ const symlinkPath = ctx.config.scaffold && ctx.config.scaffold.symlink;
94
+ if (symlinkPath) {
95
+ ctx.setSymlink(symlinkPath, ctx.worktreePath);
96
+ }
97
+ },
98
+
99
+ async onPreNuke(ctx) {
100
+ const symlinkPath = ctx.config.scaffold && ctx.config.scaffold.symlink;
101
+ if (symlinkPath && fs.existsSync(symlinkPath)) {
102
+ try {
103
+ const lstat = fs.lstatSync(symlinkPath);
104
+ if (lstat.isSymbolicLink()) {
105
+ const currentTarget = fs.realpathSync(symlinkPath);
106
+ if (path.resolve(currentTarget) === path.resolve(ctx.worktreePath)) {
107
+ console.log(`==> Unlinking active web symlink "${symlinkPath}" pointing to nuked worktree...`);
108
+ fs.unlinkSync(symlinkPath);
109
+ }
110
+ }
111
+ } catch (e) {}
112
+ }
113
+ },
114
+ },
115
+ };
@@ -0,0 +1,50 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ module.exports = {
5
+ name: 'node',
6
+
7
+ detect(cwd) {
8
+ return fs.existsSync(path.join(cwd, 'package.json'));
9
+ },
10
+
11
+ repo: {
12
+ defaultBaseBranch: 'develop',
13
+ protectedBranches: ['main', 'master', 'develop', 'prod', 'staging', 'production'],
14
+ },
15
+
16
+ workspace: {
17
+ labelPrefix: '',
18
+ defaultFocus: 'agy',
19
+ },
20
+
21
+ layout: [
22
+ { id: 'vim', title: 'vim', cmd: 'vim .', position: 'root' },
23
+ { id: 'server', title: 'npm server', cmd: 'npm run dev', split: 'right', from: 'vim' },
24
+ { id: 'shell', title: 'shell', cmd: null, split: 'down', from: 'vim' },
25
+ { id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'server', focus: true, isAgent: true },
26
+ ],
27
+
28
+ hooks: {
29
+ async onScaffold(ctx) {
30
+ // 1. Copy .env from root if available
31
+ ctx.copyFromRoot('.env', '.env');
32
+
33
+ // 2. Install dependencies
34
+ const installCmd = (ctx.config.scaffold && ctx.config.scaffold.install !== undefined)
35
+ ? ctx.config.scaffold.install
36
+ : 'npm install';
37
+
38
+ if (installCmd) {
39
+ console.log(`Running dependency installation: "${installCmd}" in "${ctx.worktreePath}"...`);
40
+ try {
41
+ ctx.exec(installCmd);
42
+ } catch (err) {
43
+ console.warn(`Warning: Dependency installation exited with an error: ${err.message}`);
44
+ }
45
+ } else {
46
+ console.log('Skipping dependency installation (scaffold.install is disabled).');
47
+ }
48
+ },
49
+ },
50
+ };
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: arise
3
+ description: User guide, CLI reference, and command executor for the `arise` utility (Git worktree and Herdr workspace orchestrator). Activate this skill whenever the user asks questions about how to use arise, how worktree orchestration works, how to configure `.ariserc.json` or `.worktreerc.json`, or asks the assistant to create, switch to, or nuke/clean up Git worktrees and Herdr workspaces on their behalf.
4
+ ---
5
+
6
+ # Arise Operator & Assistant Guide
7
+
8
+ Use this skill to answer questions about `arise` and run worktree management commands on the user's behalf.
9
+
10
+ ---
11
+
12
+ ## 1. CLI Quick Reference & Cheatsheet
13
+
14
+ ### Creating Worktrees
15
+ ```bash
16
+ # Create or open a worktree for a branch (auto-detects preset, boots Herdr workspace)
17
+ arise --branch <branch-name>
18
+
19
+ # Create worktree based off a specific base branch (e.g. develop, prod, main)
20
+ arise --branch <branch-name> --base <base-branch>
21
+
22
+ # Custom directory name or workspace name
23
+ arise -b <branch-name> -d <dir-name> -w <workspace-name>
24
+
25
+ # Explicit preset or pane focus ('agy', 'vim', 'logs', 'server', 'shell')
26
+ arise -b <branch-name> --preset laravel --focus agy
27
+ ```
28
+
29
+ ### Nuking / Teardown
30
+ ```bash
31
+ # Nuke active worktree (when run inside a worktree directory)
32
+ arise --nuke
33
+
34
+ # Nuke specific worktree by branch or directory name
35
+ arise --nuke <branch-or-dir>
36
+
37
+ # Remove directory only (keep local and remote git branches)
38
+ arise --nuke <target> --dir-only
39
+
40
+ # Delete local branch and directory, but keep remote branch on origin
41
+ arise --nuke <target> --keep-remote
42
+
43
+ # Force removal even if uncommitted changes exist
44
+ arise --nuke <target> --force
45
+ ```
46
+
47
+ ---
48
+
49
+ ## 2. Configuration (`.ariserc.json` / `.worktreerc.json` / `arise.config.js`)
50
+
51
+ Projects can configure custom topology, bare repos, and layouts via `.ariserc.json` or `.worktreerc.json` in the repository root or base directory:
52
+
53
+ ```json
54
+ {
55
+ "preset": "laravel",
56
+ "repo": {
57
+ "bareRepo": "/path/to/bare.git",
58
+ "worktreesBase": "/path/to/worktrees",
59
+ "defaultBaseBranch": "develop",
60
+ "protectedBranches": ["main", "master", "develop", "prod", "staging"]
61
+ },
62
+ "workspace": {
63
+ "labelPrefix": "[API] ",
64
+ "defaultFocus": "agy"
65
+ },
66
+ "scaffold": {
67
+ "envSource": "/path/to/shared/.env",
68
+ "symlink": "/path/to/webserver/symlink",
69
+ "install": "composer install --no-interaction"
70
+ }
71
+ }
72
+ ```
73
+
74
+ ---
75
+
76
+ ## 3. How to Assist the User
77
+
78
+ ### When the User Asks Questions:
79
+ 1. Consult the CLI options and configuration schema above.
80
+ 2. If working inside a repository with local documentation or `.ariserc.json` / `.worktreerc.json`, inspect those files.
81
+ 3. Provide clear explanations with executable CLI examples and config snippets.
82
+
83
+ ### When the User Asks You to Perform an Action:
84
+ 1. Formulate the appropriate `arise` CLI command.
85
+ 2. Execute the command on behalf of the user using the available command runner.
86
+ 3. Confirm the status of the created or nuked worktree and Herdr workspace.
package/types.d.ts ADDED
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Arise - Type Definitions
3
+ *
4
+ * Provides strict structural contracts for presets, configuration,
5
+ * declarative layouts, execution context, and lifecycle hooks.
6
+ */
7
+
8
+ export interface CliFlags {
9
+ isCleanup: boolean;
10
+ cleanupTarget: string | null;
11
+ dirOnly: boolean;
12
+ keepRemote: boolean;
13
+ force: boolean;
14
+ yes: boolean;
15
+ branch: string | null;
16
+ dirname: string | null;
17
+ workspaceName: string | null;
18
+ source: string | null;
19
+ presetName: string | null;
20
+ agent: string | null;
21
+ focusTarget: string | null;
22
+ installSkill: boolean;
23
+ skillScope: 'global' | 'local' | null;
24
+ showHelp: boolean;
25
+ showVersion: boolean;
26
+ rawArgs: string[];
27
+ }
28
+
29
+ export type SplitDirection = 'right' | 'down';
30
+
31
+ export interface PaneDefinition {
32
+ /** Unique ID for the pane within this layout */
33
+ id: string;
34
+ /** Display title for the pane in Herdr */
35
+ title: string;
36
+ /** Command to execute upon creation (or null for empty shell) */
37
+ cmd: string | null;
38
+ /** Position if this is the root pane ('root') */
39
+ position?: 'root';
40
+ /** ID of parent pane from which to split */
41
+ from?: string;
42
+ /** Direction to split ('right' | 'down') */
43
+ split?: SplitDirection;
44
+ /** Whether to focus this pane by default */
45
+ focus?: boolean;
46
+ /** Whether this pane is designated as the AI CLI agent pane */
47
+ isAgent?: boolean;
48
+ }
49
+
50
+ export interface RepoConfig {
51
+ /** Path to bare repository (if using bare git topology) */
52
+ bareRepo?: string | null;
53
+ /** Base directory where worktrees should be placed */
54
+ worktreesBase?: string | null;
55
+ /** Default base branch to branch off (e.g. 'develop', 'prod', 'main') */
56
+ defaultBaseBranch?: string;
57
+ /** Array of branch names protected against deletion */
58
+ protectedBranches?: string[];
59
+ }
60
+
61
+ export interface WorkspaceConfig {
62
+ /** Prefix added to Herdr workspace labels (e.g. '[BE] ') */
63
+ labelPrefix?: string;
64
+ /** CLI AI agent to run in the workspace pane ('agy', 'claude', 'aider', 'copilot', 'none', etc.) */
65
+ agent?: string | { cmd: string; title?: string; [key: string]: any } | null;
66
+ /** Default pane to focus ('agent', 'agy', 'claude', 'vim', 'logs', 'server', 'shell') */
67
+ defaultFocus?: string;
68
+ }
69
+
70
+ export interface ScaffoldConfig {
71
+ /** Path to environment file (.env) to copy */
72
+ envSource?: string | null;
73
+ /** Target symlink path to point to the active worktree (e.g. '/var/www/my-app') */
74
+ symlink?: string | null;
75
+ /** Command to run to install dependencies upon creation, or false/null to skip installation */
76
+ install?: string | boolean | null;
77
+ [key: string]: any;
78
+ }
79
+
80
+ export interface ExecutionContext {
81
+ /** Target worktree absolute directory */
82
+ worktreePath: string;
83
+ /** Repository root directory */
84
+ repoRoot: string | null;
85
+ /** Bare repo directory (if applicable) */
86
+ bareRepo: string | null;
87
+ /** Target branch name */
88
+ branch: string;
89
+ /** Base source branch */
90
+ source: string;
91
+ /** Parsed CLI flags */
92
+ flags: CliFlags;
93
+ /** Active preset */
94
+ preset: Preset;
95
+ /** Merged configuration */
96
+ config: WorktreeConfig;
97
+
98
+ log(msg: string): void;
99
+ warn(msg: string): void;
100
+ error(msg: string): void;
101
+
102
+ /** Execute a shell command synchronously */
103
+ exec(command: string, options?: import('child_process').ExecSyncOptions): Buffer | string;
104
+ /** Spawn a process synchronously with stdio inherit */
105
+ spawn(command: string, args?: string[], options?: import('child_process').SpawnSyncOptions): import('child_process').SpawnSyncReturns<Buffer>;
106
+ /** Copy file from src to dst */
107
+ copyFile(src: string, dst: string): boolean;
108
+ /** Copy file from repository root to worktree destination */
109
+ copyFromRoot(relativeSrc: string, relativeDst?: string): boolean;
110
+ /** Safely create or replace a symlink */
111
+ setSymlink(symlinkPath: string, target?: string): boolean;
112
+ }
113
+
114
+ export interface PresetHooks {
115
+ /** Called to sync/reset primary branches before creation */
116
+ onSyncPrimary?(ctx: ExecutionContext): Promise<void> | void;
117
+ /** Called to scaffold environment, dependencies, and permissions */
118
+ onScaffold?(ctx: ExecutionContext): Promise<void> | void;
119
+ /** Called before deleting worktree and closing workspaces */
120
+ onPreNuke?(ctx: ExecutionContext): Promise<void> | void;
121
+ /** Called after all worktree and branch deletions complete */
122
+ onPostNuke?(ctx: ExecutionContext): Promise<void> | void;
123
+ }
124
+
125
+ export interface Preset {
126
+ /** Unique name of the preset ('node', 'laravel', 'generic') */
127
+ name: string;
128
+ /** Detection rule to determine if this preset applies to a directory */
129
+ detect?(cwd: string): boolean;
130
+ /** Repository defaults */
131
+ repo?: RepoConfig;
132
+ /** Workspace defaults */
133
+ workspace?: WorkspaceConfig;
134
+ /** Declarative terminal layout */
135
+ layout?: PaneDefinition[];
136
+ /** Scaffolding defaults */
137
+ scaffold?: ScaffoldConfig;
138
+ /** Lifecycle hook implementations */
139
+ hooks?: PresetHooks;
140
+ }
141
+
142
+ export interface WorktreeConfig {
143
+ /** Name of preset or preset object */
144
+ preset?: string | Preset;
145
+ /** Repository configuration */
146
+ repo: RepoConfig;
147
+ /** Workspace configuration */
148
+ workspace: WorkspaceConfig;
149
+ /** Declarative terminal layout */
150
+ layout: PaneDefinition[];
151
+ /** Scaffolding configuration */
152
+ scaffold: ScaffoldConfig;
153
+ /** Lifecycle hook overrides */
154
+ hooks: PresetHooks;
155
+ /** Path to config file that was loaded (if any) */
156
+ configFile?: string | null;
157
+ }