aligndev 0.19.0 → 0.20.1

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/README.md CHANGED
@@ -1,168 +1,72 @@
1
1
  # aligndev
2
2
 
3
- The AlignFirst Dev Kit CLI. It carries the assistant's playbook, runs a coding agent through [AlignFirst](https://github.com/paleo/alignfirst) protocols, and keeps the inventory of the host's projects and their port ranges. The assistant is an OpenClaw bot, or a coding agent working on one project.
3
+ Autonomous software development. You say what you want; the assistant drives the coding agents and brings you the decisions.
4
4
 
5
- Run `aligndev` through `npx` (`npx -y aligndev …`), or install it with `npm install -g aligndev`. Through `npx`, it runs `alignfirst` through `npx` too, and its help and guides print both commands in that form. A global `aligndev` needs the `alignfirst` CLI on `PATH`: `npm install -g alignfirst`.
5
+ You talk to an **assistant**, the way you would to a developer on your team. It never touches the code itself. It isolates each task in its own workspace, then hands the investigation and the coding to an AI coding agent, **the agent**, which follows the [AlignFirst](https://alignfirst.paroi.tech/) protocols: specify, plan, implement, review. The assistant reads the agent's work, tests it, asks you what only you can decide, and opens the pull request.
6
6
 
7
- Supported systems: Linux and macOS, and Windows through WSL.
7
+ `aligndev` is the assistant's CLI. It gives the assistant its playbook, launches and tracks the agent, and keeps the inventory of your projects. You never run it yourself: the assistant does.
8
8
 
9
- ## Commands
9
+ ## Two Ways to Run the Assistant
10
10
 
11
- ```sh
12
- aligndev code <command> [<options>] # run a coding agent through AlignFirst protocols
13
- aligndev project <command> [<options>] # list projects, check the inventory, claim port ranges
14
- aligndev guide [<topic>] # print the playbook and the guides
15
- aligndev --help
16
- aligndev --version
17
- ```
18
-
19
- Each command prints its own usage with `--help`.
11
+ **In your coding agent.** A Claude Code or Codex session becomes the assistant of the repository it starts in. You keep chatting in the same session; the agents it launches work in the background.
20
12
 
21
- ### `aligndev code`
22
-
23
- ```sh
24
- aligndev code new --protocol spec --ticket AB-123 --message "Feature description"
25
- aligndev code resume <sessionId> --protocol plan
26
- aligndev code new --message "Execute the plan: .plans/AB-123/A2-plan.md"
27
- aligndev code new --protocol aad --no-ticket --message "Task description"
28
- aligndev code new --ticket AB-123 --catchup --protocol aad --message-file message.md
29
- aligndev code status .plans/AB-123/_aligndev/20260829-135529.md
30
- aligndev code quota
31
- ```
13
+ **As an OpenClaw bot.** The [AlignFirst Dev Kit](https://alignfirst.paroi.tech/openclaw-dev-kit) deploys the assistant on a server, under its own name in Slack or Discord. It works on every project of the host, one thread per task.
32
14
 
33
- The coding agent `aligndev code` launches is **the coder**. Run `aligndev code` from the root of the target project. The project must have a `.plans/` directory, in its repository or in its companion directory.
15
+ Either way, the agent is Claude Code or Codex. A coding-agent assistant launches its own kind by default.
34
16
 
35
- `aligndev code` reads the project's layout from `alignfirst config --json`. Session files go under its `_aligndev` location: `<ticket>/_aligndev/` or `_aligndev/`, below the project's `.plans/` unless the companion holds a separate tree. When some of the project's AlignFirst files exist in its companion, the normal permission modes make the companion writable for the coder (`--add-dir`), and a new session's prompt starts with the `alignfirst context` output.
17
+ ## Start in Claude Code or Codex
36
18
 
37
- A new protocol session needs a ticket. `--no-ticket` reserves the next side ticket through `alignfirst ticket --side` and passes it to the coder.
19
+ The project needs an AlignFirst `.plans` directory, in its repository or in a [companion directory](https://github.com/paleo/alignfirst/tree/main/packages/alignfirst#companion-directories). The [setup guide](https://github.com/paleo/alignfirst/blob/main/skills/alignfirst-setup-guide/references/coding-agent-assistant.md) prepares it.
38
20
 
39
- `--catchup` loads the ticket's history (through `alignfirst ticket --catchup`) before the protocol and message. Alone, it returns a short synthesis.
21
+ The agent needs network access and writes outside the repository, so the assistant launches it outside its sandbox: approve that request when it comes.
40
22
 
41
- `--message-file <path>` reads the message from a UTF-8 file, or from stdin with `-`. The prompt reaches the coder through stdin.
23
+ ### With the Skill
42
24
 
43
- `aligndev code status` reconciles and shows a run's durable status. It accepts a session file under `_aligndev/` or `<ticket>/_aligndev/` of the `_aligndev` location, or selects the newest run with `--ticket <id>`, `--no-ticket` or `--meta <key>`. If a recorded process is gone, it seals the session file as `status: failed`, `exitReason: terminated`. Linux records also store the process start time to detect pid reuse. Its `contextTokens` line reports what the run left in the coder's context window; a resumed session keeps growing across runs.
44
-
45
- `aligndev code quota` shows the selected coding agent's account limits, consumed percentages, and reset times. It works outside a project.
46
-
47
- ### `aligndev project`
25
+ Install the `aligndev` skill:
48
26
 
49
27
  ```sh
50
- aligndev project list [--json] [--root <path>]
51
- aligndev project doctor [--root <path>]
52
- aligndev project status <path> [--json] [--root <path>]
53
- aligndev project init [--root <path>] [--description <text>] [--port-range [<code>=]<first>-<last>]...
54
- aligndev project free-ports --size <n> [--range <code>] [--json] [--root <path>]
28
+ npx skills add https://github.com/paleo/alignfirst --global --skill aligndev
55
29
  ```
56
30
 
57
- A projects directory groups projects and optional nested projects directories. Its `.alignfirst-projects.json` marker holds an optional description and port ranges. A direct child that is a Git main worktree is a project; linked Git worktrees are listed as its workspaces. A child outside Git with a root `.alignfirst.json` is an inventory issue. `list --json` and `status` report each project's companion directory and the location of its AlignFirst files. Two projects sharing one companion directory is an inventory issue.
31
+ Then start a session in the project and invoke `/aligndev` in Claude Code, or `$aligndev` in Codex.
58
32
 
59
- ```json
60
- {
61
- "description": "Every project is a direct child of ~/projects.",
62
- "portRanges": [
63
- { "first": 28000, "last": 28599, "description": "Web projects, exposed through the gateway." },
64
- { "code": "local", "first": 29000, "last": 29199, "description": "Desktop apps, never exposed." }
65
- ]
66
- }
67
- ```
68
-
69
- `--root` defaults to `projectsRoot` in the config, then to the working directory. `doctor` is a read-only health gate: it succeeds only when discovery completes with no inventory issue.
33
+ ### Without a Skill
70
34
 
71
- An older marker carrying `"portRange": { ... }` is rejected; replace the key with `"portRanges": [{ ... }]`. Project configuration in `.alignfirst.json` keeps its singular `portRange` key.
35
+ Add this section to your global agent instructions (`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, or the equivalent):
72
36
 
73
- ### `aligndev guide`
37
+ ```markdown
38
+ ## Aligndev
74
39
 
75
- ```sh
76
- aligndev guide [<topic>] [--root <path>]
40
+ When the user mentions **aligndev**, follow the `aligndev` playbook if it is already in context. Otherwise, run `npx -y aligndev guide` and follow it.
77
41
  ```
78
42
 
79
- Each topic renders the variant of the configured `platform`.
43
+ Then ask your session to work with aligndev.
80
44
 
81
- | Topic | Output | Platforms |
82
- |-------|--------|-----------|
83
- | (none) | The playbook dispatcher. | both |
84
- | `working-session` | The work procedure: a thread under OpenClaw, the conversation for a coding agent. | both |
85
- | `project-workspace-setup`, `consultation` | Runbooks. | both |
86
- | `code` | The delegation guide. | both |
87
- | `channel-handling` | The channel and DM procedure. | `openclaw` |
88
- | `project-lifecycle` | The runbook to create, onboard or remove a project. | `openclaw` |
89
- | `slack-message-tool`, `discord-message-tool` | Extended `message` references. | `openclaw` |
90
- | `project` | The projects guide, followed by the directory sections when the root carries a marker. `--root` overrides `projectsRoot`. | `openclaw` |
45
+ ### Requirements
91
46
 
92
- Under `openclaw`, the playbook topics require `projectsRoot`.
47
+ - Node.js 22.11 or later. The assistant runs `aligndev` through `npx`, so there is nothing to install.
48
+ - Claude Code or Codex installed and logged in: `claude` then `/login`, or `codex login`.
49
+ - Linux or macOS, or Windows through WSL.
93
50
 
94
51
  ## Configuration
95
52
 
96
- `aligndev` reads one file, `~/.config/alignfirst/aligndev.config.json`. The path is fixed: no environment variable overrides it. `aligndev code` and `aligndev guide` require it, `--help` included. Without it, they fail with an error naming the path and the required keys. `aligndev project`, `aligndev --help` and `aligndev --version` run without it.
97
-
98
- ```json
99
- {
100
- "platform": "openclaw",
101
- "projectsRoot": "~/projects",
102
- "code": {
103
- "agent": "claude",
104
- "models": ["opus", "sonnet"],
105
- "skipPermissions": false,
106
- "unset": ["ANTHROPIC_API_KEY"]
107
- }
108
- }
109
- ```
110
-
111
- A coding agent acting as the assistant needs only the required keys:
53
+ `~/.alignfirst/aligndev.config.json` is optional. Without it, every key takes its default. For example, to launch Codex as the agent, whatever runs the assistant:
112
54
 
113
55
  ```json
114
56
  {
115
- "platform": "codingAgent",
116
57
  "code": { "agent": "codex" }
117
58
  }
118
59
  ```
119
60
 
120
- - `platform` — required. Selects the variant of every `aligndev guide` topic: `openclaw` for an OpenClaw assistant, `codingAgent` for a coding agent acting as the assistant.
121
- - `projectsRoot` — the default projects directory of `aligndev project` and `aligndev guide project`. Required by the `openclaw` playbook. `~/` expands to the home directory; a relative path resolves against the config file's directory.
122
- - `code.agent` — required. The coder: `claude` or `codex`.
123
- - `code.models` — replaces the selected agent's accepted models.
124
- - `code.skipPermissions` — `true` selects each CLI's dangerous permission-bypass flag. Default: `false`.
125
- - `code.unset` — environment variables stripped from the coder's environment. `aligndev code` always strips the assistant session's identity variables first (`CLAUDECODE`, `CLAUDE_CODE_SESSION_ID`, `CODEX_THREAD_ID`, …), whatever the agent.
126
-
127
- Unknown keys are rejected. An unreadable file, invalid JSON or an invalid value fails every command that loads the config, with an error naming the file.
128
-
129
- Companion directories are declared in `~/.config/alignfirst/companions.json`, which the `alignfirst` CLI reads for both tools. See [its README](https://github.com/paleo/alignfirst/tree/main/packages/alignfirst#companion-directories).
130
-
131
- ## Execution model
132
-
133
- `aligndev code` runs the coder as a direct **foreground** child of its own process. It streams a live transcript to stdout and to a per-run session file, whose frontmatter status goes from `running` to `succeeded` or `failed`, and blocks until the coder exits. It never backgrounds or detaches itself.
134
-
135
- Coding runs can be very long, so the caller always runs `aligndev code` as a background task and owns the backgrounding, as `aligndev guide code` prescribes:
136
-
137
- - Under OpenClaw, the assistant invokes it through the `exec` tool with `background: true` and `timeoutSeconds: 0`, and chains `openclaw system event --mode now --session-key <key>` onto the command.
138
- - A coding-agent assistant starts it with its own background-execution facility, with no time limit, and outside its sandbox. When the agent wakes the session as the command exits, as Claude Code does, the assistant handles the completion on that wake. Otherwise, the assistant checks its pending runs at the start of the next user turn.
139
-
140
- The completion turn locates the session file with `aligndev code status` and reads the result. The session file is the durable result handoff: frontmatter `sessionId` and status, and the `---- Result ----` block.
141
-
142
- If `aligndev code` is terminated, its signal handlers seal the session file (`status: failed`, `exitReason: terminated`), then send `SIGTERM` to the coder. After a short grace period, a `SIGKILL` guarantees no orphan is left behind. Only a `SIGKILL` of `aligndev` itself can leave a stale `running` status, which the next `status` call seals.
143
-
144
- When the coding agent's session on the host is missing or expired, `aligndev code` detects the authentication failure in its stream, seals the session file with `exitReason: auth_required`, and exits `2` with a one-line stderr message.
145
-
146
- ## Coding agents
147
-
148
- Install the selected CLI and authenticate it on the host: run `claude`, then `/login`, for Claude Code; run `codex login` for Codex.
149
-
150
- Normal runs use Claude's `--permission-mode auto` or Codex's `--sandbox workspace-write`.
151
-
152
- Claude's default model list is `fable`, `opus`, `sonnet`, `haiku`. Codex's is `astra`, `sol`, `terra`, `luna`; `aligndev code` resolves a selected Codex alias against `codex debug models --bundled`. Set `code.models` to narrow the list or to advertise an explicit Codex slug such as `gpt-5.6-terra`.
153
-
154
- Session files record `agent`. A session resumes only with the same selected agent. Agentless legacy sessions stay readable but require a new session.
155
-
156
- ## The `aligndev` skill
157
-
158
- The `aligndev` agent skill makes a Claude Code or Codex session the assistant of the project it runs in. Invoked as `/aligndev` in Claude Code, or `$aligndev` in Codex, it loads the playbook through `npx -y aligndev guide`, which needs `platform: "codingAgent"` in the config. Install it:
159
-
160
- ```sh
161
- npx skills add https://github.com/paleo/alignfirst --global --skill aligndev
162
- ```
61
+ - `platform` — `codingAgent` (default) for a coding-agent assistant, `openclaw` for an OpenClaw bot.
62
+ - `projectsRoot` — the projects directory of an OpenClaw host. Required by `openclaw`. `~/` expands to the home directory; a relative path resolves against `~/.alignfirst/`.
63
+ - `code.agent` — the agent: `claude` or `codex`. Default: the coding agent that runs the assistant, detected from its environment. Set it to launch the other one, or when both are detected, as with Codex in a VS Code terminal where the Claude Code extension is installed.
64
+ - `code.models` — the models the assistant may choose from. Default: Claude's `fable`, `opus`, `sonnet`, `haiku`, or Codex's `astra`, `sol`, `terra`, `luna`.
65
+ - `code.skipPermissions` — `true` runs Claude Code with `--dangerously-skip-permissions` instead of `--permission-mode auto`, and Codex with `--dangerously-bypass-approvals-and-sandbox` instead of `--sandbox workspace-write`. Default: `false`.
66
+ - `code.unset` — environment variables to hide from the agent, such as an API key meant for another tool on the host.
163
67
 
164
- The setup guide's [coding-agent assistant reference](https://github.com/paleo/alignfirst/blob/main/skills/alignfirst-setup-guide/references/coding-agent-assistant.md) covers the project, the configuration and the sandbox.
68
+ Unknown keys and invalid values are errors.
165
69
 
166
- ## Port claims
70
+ ## Under the Hood
167
71
 
168
- Run `aligndev project free-ports --size <n>` with the block size required by the project's workspace scheme: `perWorkspace × maxWorkspaces`. A marker entry without a code is the default range. Pass `--range <code>` to select a coded range. The setup guide writes the returned block as `portRange` in the project's `.alignfirst.json`.
72
+ `aligndev` documents itself for the assistant: `aligndev --help`, then `aligndev guide`, print everything it needs. Maintainers: see [aligndev Architecture](https://github.com/paleo/alignfirst/blob/main/docs/aligndev-architecture.md).
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import { CODING_AGENTS } from "./code/coding-agent.js";
5
5
  import { resolveExecutableModel } from "./code/models.js";
6
6
  import { readQuota } from "./code/quota.js";
7
7
  import { resolveCommandForms } from "./command-form.js";
8
- import { loadConfig, missingConfigMessage, PLATFORMS } from "./config.js";
8
+ import { loadConfig, PLATFORMS } from "./config.js";
9
9
  import { errorMessage } from "./errors.js";
10
10
  import { runGuide } from "./guide/guide-cli.js";
11
11
  import { runProject } from "./project/project-cli.js";
@@ -25,23 +25,18 @@ export async function main(options) {
25
25
  ctx.stderr.write(`${error}\n\n${renderHelp(ctx.forms.aligndev)}`);
26
26
  return 1;
27
27
  }
28
- let config;
29
28
  try {
30
- config = loadConfig(ctx.home);
29
+ const config = loadConfig(ctx.home);
30
+ if (command === "project")
31
+ return runProject(tokens, config, ctx);
32
+ if (command === "code")
33
+ return await runCode(tokens, config, ctx);
34
+ return runGuide(tokens, config, ctx);
31
35
  }
32
36
  catch (error) {
33
37
  ctx.stderr.write(`${errorMessage(error)}\n`);
34
38
  return 1;
35
39
  }
36
- if (command === "project")
37
- return runProject(tokens, config, ctx);
38
- if (config === undefined) {
39
- ctx.stderr.write(`${missingConfigMessage(ctx.home)}\n`);
40
- return 1;
41
- }
42
- if (command === "code")
43
- return runCode(tokens, config, ctx);
44
- return runGuide(tokens, config, ctx);
45
40
  }
46
41
  function renderHelp(aligndev) {
47
42
  const usage = renderUsageRows([
@@ -61,8 +56,9 @@ ${usage}
61
56
 
62
57
  Run \`${aligndev} <command> --help\` for the usage of a command.
63
58
 
64
- Config: ~/.config/alignfirst/aligndev.config.json, with "platform" (${PLATFORMS.join(" or ")}) and
65
- "code.agent" (${CODING_AGENTS.join(" or ")}). \`${aligndev} code\` and \`${aligndev} guide\` require it.
59
+ Config (optional): ~/.alignfirst/aligndev.config.json. "platform" (${PLATFORMS.join(" or ")})
60
+ defaults to codingAgent. "code.agent" (${CODING_AGENTS.join(" or ")}) defaults to the coding agent that
61
+ runs aligndev, detected from its environment.
66
62
  `;
67
63
  }
68
64
  // Each row is a command, optionally followed by its description in an aligned column.
@@ -1,5 +1,5 @@
1
1
  import type { CommandForms } from "../command-form.js";
2
- import type { AligndevConfig, CodeConfig } from "../config.js";
2
+ import { type CodeConfig, type LoadedConfig } from "../config.js";
3
3
  import type { Output } from "../output.js";
4
4
  import { type ProjectReport } from "../project/layout.js";
5
5
  import { type CodingAgent } from "./coding-agent.js";
@@ -51,7 +51,7 @@ export interface SessionArgs {
51
51
  model?: string;
52
52
  meta?: string;
53
53
  }
54
- export declare function runCode(tokens: string[], config: AligndevConfig, ctx: CodeContext): Promise<number>;
54
+ export declare function runCode(tokens: string[], config: LoadedConfig, ctx: CodeContext): Promise<number>;
55
55
  export declare function parseCodeArgs(tokens: string[], aligndev: string): CodeCommand;
56
56
  export declare function validateSessionArgs(args: SessionArgs, models: readonly string[]): string | undefined;
57
57
  export declare function checkLaunchGuards(args: SessionArgs, agent: CodingAgent, realCwd: string, records: SessionRecord[]): string | undefined;
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, realpathSync } from "node:fs";
2
2
  import { extname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
3
  import { parseArgs } from "node:util";
4
4
  import { loadCatchup, loadContext, openTicket, reserveSideTicket } from "../alignfirst-cli.js";
5
+ import { resolveCodeConfig } from "../config.js";
5
6
  import { errorMessage } from "../errors.js";
6
7
  import { companionInUse, readProjectReport, } from "../project/layout.js";
7
8
  import { createAgentAdapter } from "./coding-agent.js";
@@ -22,7 +23,7 @@ const SESSION_OPTIONS = {
22
23
  meta: { type: "string" },
23
24
  help: { type: "boolean", short: "h", default: false },
24
25
  };
25
- // Items whose companion copy the coder may edit: the companion becomes a writable directory.
26
+ // Items whose companion copy the agent may edit: the companion becomes a writable directory.
26
27
  const WRITABLE_COMPANION_ITEMS = [
27
28
  ".alignfirst.json",
28
29
  ".alignfirst.md",
@@ -30,7 +31,7 @@ const WRITABLE_COMPANION_ITEMS = [
30
31
  "docs",
31
32
  ".plans",
32
33
  ];
33
- // Items the coder would not find in the repository: a new session gets `alignfirst context`.
34
+ // Items the agent would not find in the repository: a new session gets `alignfirst context`.
34
35
  const CONTEXT_COMPANION_ITEMS = [
35
36
  ".alignfirst.json",
36
37
  ".alignfirst.md",
@@ -44,7 +45,7 @@ export async function runCode(tokens, config, ctx) {
44
45
  const command = parseCodeArgs(tokens, ctx.forms.aligndev);
45
46
  if (command.kind === "status")
46
47
  return showStatus(ctx, command.target);
47
- const { code } = config;
48
+ const code = resolveCodeConfig(config, ctx.env);
48
49
  if (command.kind === "quota")
49
50
  return await showQuota(ctx, code);
50
51
  const models = resolveModels(code.agent, code.models);
@@ -610,8 +611,9 @@ Options (new, resume):
610
611
 
611
612
  ${requires}
612
613
 
613
- Config (~/.config/alignfirst/aligndev.config.json):
614
- code.agent Required coding agent: claude or codex (selected: ${agent}).
614
+ Config (~/.alignfirst/aligndev.config.json):
615
+ code.agent Coding agent: claude or codex (selected: ${agent}). Defaults to the coding
616
+ agent that runs aligndev.
615
617
  code.models List replacing the models accepted by --model.
616
618
  code.skipPermissions true to run the coding agent with permission prompts disabled.
617
619
  code.unset Env vars to strip from the coding agent child.
@@ -2,3 +2,4 @@ import type { AgentAdapter } from "./run-agent.js";
2
2
  export declare const CODING_AGENTS: readonly ["claude", "codex"];
3
3
  export type CodingAgent = (typeof CODING_AGENTS)[number];
4
4
  export declare function createAgentAdapter(agent: CodingAgent): AgentAdapter;
5
+ export declare function detectCodingAgents(env: NodeJS.ProcessEnv): CodingAgent[];
@@ -4,3 +4,13 @@ export const CODING_AGENTS = ["claude", "codex"];
4
4
  export function createAgentAdapter(agent) {
5
5
  return agent === "claude" ? createClaudeAdapter() : createCodexAdapter();
6
6
  }
7
+ // Each coding agent marks the commands it runs: Claude Code sets `CLAUDECODE=1` (documented), and
8
+ // Codex sets `CODEX_THREAD_ID`.
9
+ export function detectCodingAgents(env) {
10
+ const detected = [];
11
+ if (env.CLAUDECODE === "1")
12
+ detected.push("claude");
13
+ if (env.CODEX_THREAD_ID !== undefined && env.CODEX_THREAD_ID !== "")
14
+ detected.push("codex");
15
+ return detected;
16
+ }
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { appendTranscript, applyCompletion } from "./session-file.js";
3
3
  const TERMINATION_GRACE_MS = 2000;
4
4
  // A coding-agent session exports its own identity to every command it runs; when the assistant
5
- // runs in one, the coder must not inherit it.
5
+ // runs in one, the agent it launches must not inherit it.
6
6
  const ASSISTANT_SESSION_VARIABLES = [
7
7
  "CLAUDECODE",
8
8
  "CLAUDE_CODE_SESSION_ID",
package/dist/config.d.ts CHANGED
@@ -1,22 +1,25 @@
1
1
  import { type CodingAgent } from "./code/coding-agent.js";
2
2
  export declare const PLATFORMS: readonly ["openclaw", "codingAgent"];
3
3
  export type Platform = (typeof PLATFORMS)[number];
4
- export interface AligndevConfig {
4
+ export interface LoadedConfig {
5
5
  path: string;
6
6
  platform: Platform;
7
7
  projectsRoot?: ProjectsRoot;
8
- code: CodeConfig;
8
+ code: LoadedCodeConfig;
9
9
  }
10
10
  export interface ProjectsRoot {
11
11
  path: string;
12
12
  written: string;
13
13
  }
14
- export interface CodeConfig {
15
- agent: CodingAgent;
14
+ export interface LoadedCodeConfig {
15
+ agent?: CodingAgent;
16
16
  models?: string[];
17
17
  skipPermissions: boolean;
18
18
  unset: string[];
19
19
  }
20
- export declare function loadConfig(home: string): AligndevConfig | undefined;
21
- export declare function missingConfigMessage(home: string): string;
22
- export declare function requireProjectsRoot(config: AligndevConfig): ProjectsRoot;
20
+ export interface CodeConfig extends LoadedCodeConfig {
21
+ agent: CodingAgent;
22
+ }
23
+ export declare function loadConfig(home: string): LoadedConfig;
24
+ export declare function resolveCodeConfig(config: LoadedConfig, env: NodeJS.ProcessEnv): CodeConfig;
25
+ export declare function requireProjectsRoot(config: LoadedConfig): ProjectsRoot;
package/dist/config.js CHANGED
@@ -1,45 +1,41 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, isAbsolute, join, resolve } from "node:path";
3
3
  import { type } from "arktype";
4
- import { CODING_AGENTS } from "./code/coding-agent.js";
4
+ import { CODING_AGENTS, detectCodingAgents } from "./code/coding-agent.js";
5
5
  import { errorMessage } from "./errors.js";
6
6
  export const PLATFORMS = ["openclaw", "codingAgent"];
7
7
  const codeSchema = type({
8
8
  "+": "reject",
9
- agent: type.enumerated(...CODING_AGENTS),
9
+ "agent?": type.enumerated(...CODING_AGENTS),
10
10
  "models?": "string[]",
11
11
  "skipPermissions?": "boolean",
12
12
  "unset?": "string[]",
13
13
  });
14
14
  const configSchema = type({
15
15
  "+": "reject",
16
- platform: type.enumerated(...PLATFORMS),
16
+ "platform?": type.enumerated(...PLATFORMS),
17
17
  "projectsRoot?": "string > 0",
18
- code: codeSchema,
18
+ "code?": codeSchema,
19
19
  });
20
- // An absent file is a normal state: a machine where aligndev is not configured.
20
+ // An absent file is a normal state: every key takes its default.
21
21
  export function loadConfig(home) {
22
- const path = configPath(home);
23
- if (!existsSync(path))
24
- return;
25
- const value = parseConfigFile(path);
22
+ const path = join(home, ".alignfirst", "aligndev.config.json");
23
+ const value = existsSync(path) ? parseConfigFile(path) : {};
24
+ const code = value.code ?? {};
26
25
  return {
27
26
  path,
28
- platform: value.platform,
27
+ platform: value.platform ?? "codingAgent",
29
28
  ...(value.projectsRoot === undefined
30
29
  ? {}
31
30
  : { projectsRoot: resolveProjectsRoot(value.projectsRoot, home, path) }),
32
31
  code: {
33
- agent: value.code.agent,
34
- ...(value.code.models === undefined ? {} : { models: value.code.models }),
35
- skipPermissions: value.code.skipPermissions ?? false,
36
- unset: value.code.unset ?? [],
32
+ ...(code.agent === undefined ? {} : { agent: code.agent }),
33
+ ...(code.models === undefined ? {} : { models: code.models }),
34
+ skipPermissions: code.skipPermissions ?? false,
35
+ unset: code.unset ?? [],
37
36
  },
38
37
  };
39
38
  }
40
- function configPath(home) {
41
- return join(home, ".config", "alignfirst", "aligndev.config.json");
42
- }
43
39
  function parseConfigFile(path) {
44
40
  let value;
45
41
  try {
@@ -61,9 +57,18 @@ function resolveProjectsRoot(written, home, path) {
61
57
  return { path: join(home, written.slice(2)), written };
62
58
  return { path: isAbsolute(written) ? written : resolve(dirname(path), written), written };
63
59
  }
64
- export function missingConfigMessage(home) {
65
- return (`Error: no aligndev config at ${configPath(home)}. Create it with "platform" ` +
66
- `(${PLATFORMS.join(" or ")}) and "code.agent" (${CODING_AGENTS.join(" or ")}).`);
60
+ // The configured `code.agent` wins; otherwise, the coding agent that runs aligndev.
61
+ export function resolveCodeConfig(config, env) {
62
+ const { agent } = config.code;
63
+ if (agent !== undefined)
64
+ return { ...config.code, agent };
65
+ const detected = detectCodingAgents(env);
66
+ if (detected.length === 1)
67
+ return { ...config.code, agent: detected[0] };
68
+ const problem = detected.length === 0
69
+ ? "no coding agent detected: run aligndev from Claude Code or Codex, or set"
70
+ : "both Claude Code and Codex detected: set";
71
+ throw new Error(`Error: ${problem} "code.agent" (${CODING_AGENTS.join(" or ")}) in ${config.path}.`);
67
72
  }
68
73
  export function requireProjectsRoot(config) {
69
74
  if (config.projectsRoot !== undefined)
@@ -11,7 +11,7 @@ export function renderCodeGuide(platform, agent, models, forms) {
11
11
  ? "The project must be prepared for AlignFirst. `npx -y aligndev` runs the `alignfirst` CLI through `npx`."
12
12
  : "The project must be prepared for AlignFirst, with the `alignfirst` CLI installed (`npm install -g alignfirst`).",
13
13
  ALIGNFIRST_USE: forms.viaNpx
14
- ? "`npx -y aligndev code` runs the `alignfirst` CLI through `npx`, and so does the coder: it runs `npx -y alignfirst guide <protocol>` in the project."
15
- : "`aligndev code` requires the `alignfirst` CLI on `PATH`. The coder runs `alignfirst guide <protocol>` in the project, so the protocols come from the installed CLI.",
14
+ ? "`npx -y aligndev code` runs the `alignfirst` CLI through `npx`, and so does the agent: it runs `npx -y alignfirst guide <protocol>` in the project."
15
+ : "`aligndev code` requires the `alignfirst` CLI on `PATH`. The agent runs `alignfirst guide <protocol>` in the project, so the protocols come from the installed CLI.",
16
16
  });
17
17
  }
@@ -1,3 +1,3 @@
1
- import { type AligndevConfig } from "../config.js";
1
+ import { type LoadedConfig } from "../config.js";
2
2
  import { type ProjectsCallerContext } from "../project/project-cli.js";
3
- export declare function runGuide(tokens: string[], config: AligndevConfig, ctx: ProjectsCallerContext): number;
3
+ export declare function runGuide(tokens: string[], config: LoadedConfig, ctx: ProjectsCallerContext): number;
@@ -1,6 +1,6 @@
1
1
  import { parseArgs } from "node:util";
2
2
  import { resolveModels } from "../code/models.js";
3
- import { PLATFORMS, requireProjectsRoot } from "../config.js";
3
+ import { PLATFORMS, requireProjectsRoot, resolveCodeConfig, } from "../config.js";
4
4
  import { errorMessage } from "../errors.js";
5
5
  import { renderProjectsGuideForRoot } from "../project/project-cli.js";
6
6
  import { renderCodeGuide } from "./code-guide.js";
@@ -53,16 +53,16 @@ ${playbookTopics.join("\n")}
53
53
  }
54
54
  function renderTopic(args, config, ctx) {
55
55
  if (args.topic === "code")
56
- return renderCodeTopic(config, ctx.forms);
56
+ return renderCodeTopic(config, ctx);
57
57
  if (args.topic === "project" && config.platform === "openclaw") {
58
58
  return renderProjectsGuideForRoot({ ...ctx, projectsRoot: config.projectsRoot }, args.root);
59
59
  }
60
60
  return renderPlaybookTopic(args.topic, config, ctx.forms);
61
61
  }
62
- function renderCodeTopic(config, forms) {
63
- const { code } = config;
62
+ function renderCodeTopic(config, ctx) {
63
+ const code = resolveCodeConfig(config, ctx.env);
64
64
  const models = resolveModels(code.agent, code.models);
65
- return renderCodeGuide(config.platform, code.agent, models, forms);
65
+ return renderCodeGuide(config.platform, code.agent, models, ctx.forms);
66
66
  }
67
67
  function renderPlaybookTopic(topic, config, forms) {
68
68
  if (topic !== undefined)
@@ -1,5 +1,5 @@
1
1
  import type { CommandForms } from "../command-form.js";
2
- import type { AligndevConfig, ProjectsRoot } from "../config.js";
2
+ import type { LoadedConfig, ProjectsRoot } from "../config.js";
3
3
  import type { Output } from "../output.js";
4
4
  export interface ProjectsCallerContext {
5
5
  cwd: string;
@@ -13,5 +13,5 @@ export interface ProjectsCallerContext {
13
13
  export interface ProjectsContext extends ProjectsCallerContext {
14
14
  projectsRoot?: ProjectsRoot;
15
15
  }
16
- export declare function runProject(tokens: string[], config: AligndevConfig | undefined, caller: ProjectsCallerContext): number;
16
+ export declare function runProject(tokens: string[], config: LoadedConfig, caller: ProjectsCallerContext): number;
17
17
  export declare function renderProjectsGuideForRoot(ctx: ProjectsContext, rootOption: string | undefined): string;
@@ -10,7 +10,7 @@ import { findFreeBlock } from "./ports.js";
10
10
  import { renderPortRangeJson, renderProjectDoctor, renderProjectDoctorFailure, renderProjectList, renderProjectListJson, renderProjectStatus, renderProjectStatusJson, } from "./render.js";
11
11
  import { getProjectStatus } from "./status.js";
12
12
  export function runProject(tokens, config, caller) {
13
- const ctx = { ...caller, projectsRoot: config?.projectsRoot };
13
+ const ctx = { ...caller, projectsRoot: config.projectsRoot };
14
14
  try {
15
15
  return runProjectCommand(ctx, parseProjectsArgs(tokens));
16
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aligndev",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "license": "CC0-1.0",
5
5
  "author": "Thomas MUR",
6
6
  "description": "The AlignFirst Dev Kit CLI: the assistant's playbook, coding-agent delegation, and project inventory.",
@@ -22,7 +22,7 @@
22
22
  "directory": "packages/aligndev"
23
23
  },
24
24
  "engines": {
25
- "node": ">=24.16.0"
25
+ "node": ">=22.11.0"
26
26
  },
27
27
  "packageManager": "npm@11.19.0",
28
28
  "type": "module",