Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pnpm run build
Arashi currently provides these commands:

- `arashi init`
- [`arashi init --zero-config`](docs/standalone.md) for a single repository with `.worktrees/<branch>` paths and no persisted Arashi configuration
- `arashi install`
- `arashi update [--check] [--dry-run] [--yes]`
- `arashi add <git-url>`
Expand Down
106 changes: 106 additions & 0 deletions contracts/cli-commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "configured-only",
"reason": "Adding child repositories requires persisted configuration."
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -93,6 +97,10 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "configured-only",
"reason": "Cloning configured child repositories requires persisted configuration."
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -229,6 +237,9 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "full"
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -259,6 +270,9 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "full"
},
"vscode": {
"expectation": "excluded",
"reason": "Diagnostics remain a terminal-focused maintenance workflow."
Expand Down Expand Up @@ -332,6 +346,10 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "configured-only",
"reason": "Cross-repository execution requires persisted repository metadata."
},
"vscode": {
"expectation": "excluded",
"reason": "Arbitrary cross-repository process execution is intentionally terminal-only."
Expand Down Expand Up @@ -405,6 +423,9 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "full"
},
"vscode": {
"expectation": "excluded",
"reason": "Agent handoff generation is intentionally terminal-only."
Expand Down Expand Up @@ -473,6 +494,13 @@
"required": true,
"optional": false,
"variadic": false
},
{
"flags": "--zero-config",
"description": "Prepare implicit standalone mode without creating configuration",
"required": false,
"optional": false,
"variadic": false
}
],
"semantics": {
Expand All @@ -485,8 +513,32 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "conditional",
"reason": "Only init --zero-config prepares standalone mode; ordinary init creates configured mode."
},
"vscode": {
"expectation": "required"
},
"zeroConfig": {
"compatibleOptions": ["--dry-run", "--json", "--verbose"],
"dryRun": {
"finalState": "unchanged",
"supported": true
},
"incompatibleOptions": [
"--force",
"--ignore-scope",
"--no-discover",
"--repos-dir",
"--worktrees-dir"
],
"json": {
"singleEnvelope": true,
"supported": true,
"suppressesHumanStdout": true
},
"option": "--zero-config"
}
}
},
Expand Down Expand Up @@ -517,6 +569,10 @@
"expectation": "excluded",
"reason": "The skill assumes Arashi is already installed; bootstrap guidance belongs in installation docs."
},
"standalone": {
"support": "not-applicable",
"reason": "Installation does not consume workspace context."
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -568,6 +624,9 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "full"
},
"vscode": {
"expectation": "represented",
"reason": "The worktree panel represents the CLI list workflow."
Expand Down Expand Up @@ -613,6 +672,9 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "full"
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -657,6 +719,9 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "full"
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -708,6 +773,10 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "configured-only",
"reason": "Coordinated pull requires persisted repository metadata."
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -766,6 +835,10 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "configured-only",
"reason": "Coordinated push requires persisted repository metadata."
},
"vscode": {
"expectation": "excluded",
"reason": "Push remains explicit terminal source-control behavior."
Expand Down Expand Up @@ -847,6 +920,9 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "full"
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -898,6 +974,10 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "configured-only",
"reason": "Repository setup coordination requires persisted repository metadata."
},
"vscode": {
"expectation": "required"
}
Expand All @@ -921,6 +1001,10 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "not-applicable",
"reason": "This command does not consume Arashi workspace context."
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -961,6 +1045,10 @@
"expectation": "represented",
"reason": "Shell initialization is covered as part of the shell workflow."
},
"standalone": {
"support": "not-applicable",
"reason": "Shell initialization does not consume workspace context."
},
"vscode": {
"expectation": "excluded",
"reason": "Shell initialization configures terminals and is not an editor command."
Expand All @@ -987,6 +1075,10 @@
"expectation": "represented",
"reason": "Shell installation is covered as part of the shell workflow."
},
"standalone": {
"support": "not-applicable",
"reason": "Shell installation does not consume workspace context."
},
"vscode": {
"expectation": "excluded",
"reason": "Shell configuration installation is outside extension scope."
Expand Down Expand Up @@ -1039,6 +1131,9 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "full"
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -1147,6 +1242,9 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "full"
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -1198,6 +1296,10 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "configured-only",
"reason": "Repository synchronization requires persisted repository metadata."
},
"vscode": {
"expectation": "required"
}
Expand Down Expand Up @@ -1250,6 +1352,10 @@
"skills": {
"expectation": "required"
},
"standalone": {
"support": "not-applicable",
"reason": "This command does not consume Arashi workspace context."
},
"vscode": {
"expectation": "required"
}
Expand Down
20 changes: 20 additions & 0 deletions docs/standalone.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Zero-config standalone workspaces

Arashi can manage one ordinary, non-bare Git repository without `.arashi/config.json`. Bootstrap the repository-local convention explicitly:

```sh
arashi init --zero-config
arashi create feat/example
arashi list
arashi status
arashi switch feat/example
arashi remove feat/example
```

Bootstrap creates the root `.worktrees/` directory and, only when Git does not already ignore the convention, appends the literal `.worktrees/` rule to the repository's common `info/exclude`. It never edits `.gitignore`, global Git configuration, or `.arashi/`. Use `--dry-run`, `--verbose`, and `--json` to preview or automate bootstrap; configuration-producing init options are incompatible with `--zero-config`.

Standalone worktrees use the exact path `.worktrees/<branch>`, including natural nested directories for branch names such as `feat/example`. Before create—even during dry-run—Arashi asks Git whether that exact destination is effectively ignored. If it is exposed by a missing or negated rule, run `arashi init --zero-config` or add `.worktrees/` to the repository-local exclude file.

Standalone mode supports `create`, `list`, `status`, `switch`, `remove`, `prune`, `doctor`, `move`, and `handoff`. Repository/group selection and coordinated child-repository commands require persisted configuration. Run ordinary `arashi init` to upgrade when you need child repositories, groups, custom worktree locations, configured defaults, or workspace/repository hooks.

The same workspace is discovered from its main worktree and linked worktrees. Existing configuration always takes precedence, and malformed or unsupported configuration is reported rather than hidden by standalone fallback.
12 changes: 11 additions & 1 deletion scripts/test/run-tests.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { dirname, join, relative } from "path";
import { readFileSync, readdirSync } from "fs";
import { mkdtempSync, readFileSync, readdirSync, rmSync } from "fs";
import { spawn } from "node:child_process";
import { fileURLToPath } from "url";
import { tmpdir } from "os";

const currentFilePath = fileURLToPath(import.meta.url);
const currentDir = dirname(currentFilePath);
Expand Down Expand Up @@ -116,6 +117,7 @@ function getHostTestFiles(): string[] {

async function main(): Promise<void> {
const selectedFiles = getHostTestFiles();
const isolatedHome = mkdtempSync(join(tmpdir(), "arashi-test-home-"));

if (selectedFiles.length === 0) {
throw new Error("No test files matched the current platform.");
Expand All @@ -126,13 +128,21 @@ async function main(): Promise<void> {
[join(workspaceRoot, "node_modules", "vitest", "vitest.mjs"), "run", ...selectedFiles],
{
cwd: workspaceRoot,
env: {
...process.env,
GIT_CONFIG_COUNT: "1",
GIT_CONFIG_KEY_0: "commit.gpgsign",
GIT_CONFIG_VALUE_0: "false",
HOME: isolatedHome,
},
stdio: "inherit",
},
);
const exitCode = await new Promise<number>((resolve, reject) => {
proc.once("error", reject);
proc.once("exit", (code) => resolve(code ?? 1));
});
rmSync(isolatedHome, { force: true, recursive: true });
process.exit(exitCode);
}

Expand Down
2 changes: 2 additions & 0 deletions src/commands/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
unknownErrorToJsonError,
writeJsonEnvelope,
} from "../lib/json-output.ts";
import { throwIfStandaloneWorkspace } from "../lib/workspace-context.ts";

type RepoConfig = Awaited<ReturnType<typeof loadConfig>>["repos"][string];

Expand Down Expand Up @@ -405,6 +406,7 @@ const executeAdd = async (
// Step 1: Validate workspace is initialized
const hasConfig = await configExists(workspaceRoot);
if (!hasConfig) {
await throwIfStandaloneWorkspace("add", workspaceRoot);
throw new AddCommandError(
'Workspace not initialized. Run "arashi init" first.',
AddCommandErrorCode.CONFIG_UPDATE_FAILED,
Expand Down
12 changes: 4 additions & 8 deletions src/commands/clone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ import {
unsupportedJsonModeError,
writeJsonEnvelope,
} from "../lib/json-output.ts";
import {
findWorkspaceRoot,
loadConfig,
normalizeConfig,
repairRepositoryGitUrls,
saveConfig,
} from "../lib/config.ts";
import { loadConfig, normalizeConfig, repairRepositoryGitUrls, saveConfig } from "../lib/config.ts";
import { info, error as logError, spinner, success, warn } from "../lib/logger.ts";
import { join, resolve } from "path";
import {
Expand All @@ -35,6 +29,7 @@ import {
type ManagedIgnoreReconciliation,
} from "../lib/managed-ignore.ts";
import { DEFAULT_WORKTREES_DIR } from "../lib/worktree-location.ts";
import { findConfiguredWorkspaceRoot } from "../lib/workspace-context.ts";

interface Choice<T> {
value: T;
Expand Down Expand Up @@ -139,7 +134,8 @@ export async function executeClone(
options: CloneCommandOptions,
deps: CloneCommandDependencies = {},
): Promise<CloneExecutionResult> {
const resolveWorkspaceRoot = deps.findWorkspaceRoot ?? findWorkspaceRoot;
const resolveWorkspaceRoot =
deps.findWorkspaceRoot ?? (() => findConfiguredWorkspaceRoot("clone"));
const readConfig = deps.loadConfig ?? loadConfig;
const writeConfig = deps.saveConfig ?? saveConfig;
const repairGitUrls = deps.repairRepositoryGitUrls ?? repairRepositoryGitUrls;
Expand Down
Loading
Loading