prloom 0.1.1 → 0.1.3

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/dist/cli/index.js CHANGED
@@ -7238,14 +7238,46 @@ var init_execa = __esm(() => {
7238
7238
  } = getIpcExport());
7239
7239
  });
7240
7240
 
7241
+ // src/lib/adapters/tmux.ts
7242
+ async function waitForTmuxSession(sessionName) {
7243
+ while (true) {
7244
+ const { exitCode } = await execa("tmux", ["has-session", "-t", sessionName], {
7245
+ reject: false
7246
+ });
7247
+ if (exitCode !== 0) {
7248
+ return;
7249
+ }
7250
+ await new Promise((resolve5) => setTimeout(resolve5, 1000));
7251
+ }
7252
+ }
7253
+ var init_tmux = __esm(() => {
7254
+ init_execa();
7255
+ });
7256
+
7241
7257
  // src/lib/adapters/codex.ts
7242
7258
  var codexAdapter;
7243
7259
  var init_codex = __esm(() => {
7244
7260
  init_execa();
7261
+ init_tmux();
7245
7262
  codexAdapter = {
7246
7263
  name: "codex",
7247
- async execute({ cwd, prompt }) {
7248
- const result = await execa("codex", ["exec", prompt, "--full-auto"], {
7264
+ async execute({ cwd, prompt, tmux }) {
7265
+ const args = ["exec", prompt, "--full-auto"];
7266
+ if (tmux) {
7267
+ await execa("tmux", [
7268
+ "new-session",
7269
+ "-d",
7270
+ "-s",
7271
+ tmux.sessionName,
7272
+ "-c",
7273
+ cwd,
7274
+ "codex",
7275
+ ...args
7276
+ ], { reject: false });
7277
+ await waitForTmuxSession(tmux.sessionName);
7278
+ return { exitCode: 0 };
7279
+ }
7280
+ const result = await execa("codex", args, {
7249
7281
  cwd,
7250
7282
  timeout: 0,
7251
7283
  reject: false
@@ -7266,9 +7298,25 @@ var init_codex = __esm(() => {
7266
7298
  var opencodeAdapter;
7267
7299
  var init_opencode = __esm(() => {
7268
7300
  init_execa();
7301
+ init_tmux();
7269
7302
  opencodeAdapter = {
7270
7303
  name: "opencode",
7271
- async execute({ cwd, prompt }) {
7304
+ async execute({ cwd, prompt, tmux }) {
7305
+ if (tmux) {
7306
+ await execa("tmux", [
7307
+ "new-session",
7308
+ "-d",
7309
+ "-s",
7310
+ tmux.sessionName,
7311
+ "-c",
7312
+ cwd,
7313
+ "opencode",
7314
+ "run",
7315
+ prompt
7316
+ ], { reject: false });
7317
+ await waitForTmuxSession(tmux.sessionName);
7318
+ return { exitCode: 0 };
7319
+ }
7272
7320
  const result = await execa("opencode", ["run", prompt], {
7273
7321
  cwd,
7274
7322
  timeout: 0,
@@ -7290,10 +7338,26 @@ var init_opencode = __esm(() => {
7290
7338
  var claudeAdapter;
7291
7339
  var init_claude = __esm(() => {
7292
7340
  init_execa();
7341
+ init_tmux();
7293
7342
  claudeAdapter = {
7294
7343
  name: "claude",
7295
- async execute({ cwd, prompt }) {
7296
- const result = await execa("claude", ["-p", prompt, "--dangerously-skip-permissions"], {
7344
+ async execute({ cwd, prompt, tmux }) {
7345
+ const args = ["-p", prompt, "--dangerously-skip-permissions"];
7346
+ if (tmux) {
7347
+ await execa("tmux", [
7348
+ "new-session",
7349
+ "-d",
7350
+ "-s",
7351
+ tmux.sessionName,
7352
+ "-c",
7353
+ cwd,
7354
+ "claude",
7355
+ ...args
7356
+ ], { reject: false });
7357
+ await waitForTmuxSession(tmux.sessionName);
7358
+ return { exitCode: 0 };
7359
+ }
7360
+ const result = await execa("claude", args, {
7297
7361
  cwd,
7298
7362
  timeout: 0,
7299
7363
  reject: false
@@ -7314,7 +7378,7 @@ var manualAdapter;
7314
7378
  var init_manual = __esm(() => {
7315
7379
  manualAdapter = {
7316
7380
  name: "manual",
7317
- async execute({ cwd, prompt }) {
7381
+ async execute({ cwd, prompt, tmux }) {
7318
7382
  console.log("⚠️ Manual agent: execute() called but should be skipped by dispatcher.");
7319
7383
  console.log(" This plan is intended for IDE-driven execution.");
7320
7384
  return { exitCode: 0 };
@@ -16690,23 +16754,32 @@ var init_prompt_sources = __esm(() => {
16690
16754
  BUILTIN_PROMPTS = {
16691
16755
  designer_new: `# Designer: Create a New Plan
16692
16756
 
16693
- You are helping a software engineer **plan** a coding task for prloom.
16757
+ You are helping a software engineer **plan** a coding task.
16694
16758
 
16695
16759
  > [!CAUTION] > **You are PLANNING, not building.**
16696
16760
  >
16697
16761
  > - Do NOT modify any code files
16698
16762
  > - The ONLY file you may edit is: \`{{plan_path}}\`
16699
16763
 
16700
- ## Your Philosophy
16764
+ ## Repository Context
16701
16765
 
16702
- You are working with a technical user who knows what they want. Your job is to **extract requirements**, not to make decisions for them.
16766
+ You are working in:
16703
16767
 
16704
- - **Ask clarifying questions** — do not assume
16705
- - **Do NOT fill in gaps** — wait for the user to provide details
16706
- - **Defer to the user's judgment** on design and implementation
16707
- - Only fill in details if the user explicitly says "fill in the details" or similar
16768
+ - **Repo**: \`{{repo_path}}\`
16769
+ - **Base branch**: \`{{base_branch}}\`
16770
+ - **Worker agent**: \`{{worker_agent}}\`
16708
16771
 
16709
- The user will give you information bit by bit. Your responsibility is to synthesize their requirements into a structured plan.
16772
+ Explore the codebase freely to understand context. Do NOT ask questions you can answer by exploring (e.g., test commands, file locations, package.json contents).
16773
+
16774
+ ## Your Philosophy
16775
+
16776
+ You are working with a technical user who knows what they want. Your job is to **extract requirements** and **discuss implementation**, not to make decisions for them.
16777
+
16778
+ - **Explore the codebase** to understand context before asking questions
16779
+ - **Ask clarifying questions** about user preferences and acceptance criteria
16780
+ - **Discuss implementation approach** — do not jump straight to writing the plan
16781
+ - **Defer to the user's judgment** on design decisions
16782
+ - Only fill in the plan when the user confirms they're ready
16710
16783
 
16711
16784
  {{#if user_description}}
16712
16785
 
@@ -16714,35 +16787,51 @@ The user will give you information bit by bit. Your responsibility is to synthes
16714
16787
 
16715
16788
  > {{user_description}}
16716
16789
 
16717
- Ask clarifying questions before filling in the plan. What files? What approach? Any constraints?
16790
+ Explore relevant files, then discuss the implementation approach with the user before writing the plan.
16718
16791
  {{else}}
16719
16792
 
16720
16793
  ## Your First Step
16721
16794
 
16722
16795
  Ask the user: **What would you like to build?**
16723
16796
 
16724
- Then ask follow-up questions:
16797
+ Then:
16725
16798
 
16726
- - How should it work?
16727
- - What files are involved?
16728
- - Any constraints or preferences?
16729
- - What are the acceptance criteria?
16730
- {{/if}}
16799
+ 1. Explore the codebase to understand the relevant areas
16800
+ 2. Discuss how to implement it (files to change, approach, trade-offs)
16801
+ 3. Confirm acceptance criteria
16802
+ 4. When the user is ready, write the plan
16803
+ {{/if}}
16731
16804
 
16732
16805
  ## Plan Structure
16733
16806
 
16734
- Once you have the requirements, fill in:
16807
+ The plan file already has a template with these sections. Fill them in:
16735
16808
 
16736
16809
  - **Objective**: What will be built (1-2 sentences)
16737
- - **Context**: Files to modify, test commands, constraints
16738
- - **TODO**: Small, sequential tasks
16810
+ - **Context**: Files to modify, constraints, any notes the Worker needs
16811
+ - **TODO (Commits)**: Each item is ONE commit the Worker will make
16739
16812
 
16740
- Leave the frontmatter alone.
16813
+ ### TODO Rules
16814
+
16815
+ Each TODO item represents a **single commit**. Think of them as the git log you want to see:
16816
+
16817
+ ✅ Good TODOs (commits):
16818
+
16819
+ - \`Fix PDF viewer to load beyond 10 pages\`
16820
+ - \`Add horizontal resize handle to Learn panel\`
16821
+ - \`Update LearnStore to trigger page prefetch\`
16822
+
16823
+ ❌ Bad TODOs (not commits):
16824
+
16825
+ - \`Run npm test\` — this is verification, not a commit
16826
+ - \`Trace the code to understand X\` — this is research, not a commit
16827
+ - \`Validate acceptance criteria\` — this is testing, not a commit
16828
+
16829
+ The Worker will run tests and type-checks before each commit automatically.
16741
16830
 
16742
- ## Runtime Context
16831
+ ## Important
16743
16832
 
16744
- - Base branch: \`{{base_branch}}\`
16745
- - Worker agent: \`{{worker_agent}}\`
16833
+ - The plan template already has a \`## Progress Log\` section — do NOT duplicate it
16834
+ - Leave the frontmatter (\`id\`, \`status\`, \`agent\`, \`base_branch\`) alone
16746
16835
  `,
16747
16836
  designer_edit: `# Designer: Edit an Existing Plan
16748
16837
 
@@ -16886,10 +16975,11 @@ function renderWorkerPrompt(repoRoot, plan, todo) {
16886
16975
  plan: plan.raw
16887
16976
  });
16888
16977
  }
16889
- function renderDesignerNewPrompt(planPath, baseBranch, workerAgent, userDescription) {
16978
+ function renderDesignerNewPrompt(repoPath, planPath, baseBranch, workerAgent, userDescription) {
16890
16979
  const template = BUILTIN_PROMPTS["designer_new"];
16891
16980
  const compiled = import_handlebars.default.compile(template);
16892
16981
  return compiled({
16982
+ repo_path: repoPath,
16893
16983
  plan_path: planPath,
16894
16984
  base_branch: baseBranch,
16895
16985
  worker_agent: workerAgent,
@@ -17266,7 +17356,7 @@ async function runNew(repoRoot, planId, agentOverride, noDesigner) {
17266
17356
  console.log(`Designer agent: ${designerAgent}`);
17267
17357
  console.log("");
17268
17358
  console.log("Starting Designer session to fill in the plan...");
17269
- const prompt = renderDesignerNewPrompt(planPath, baseBranch, workerAgent);
17359
+ const prompt = renderDesignerNewPrompt(repoRoot, planPath, baseBranch, workerAgent);
17270
17360
  await adapter.interactive({ cwd: repoRoot, prompt });
17271
17361
  console.log("Designer session ended.");
17272
17362
  console.log("Plan is now in inbox. Run 'prloom start' to dispatch.");
@@ -17556,7 +17646,7 @@ __export(exports_dispatcher, {
17556
17646
  });
17557
17647
  import { join as join9 } from "path";
17558
17648
  import { existsSync as existsSync9, statSync as statSync5 } from "fs";
17559
- async function runDispatcher(repoRoot) {
17649
+ async function runDispatcher(repoRoot, options2 = {}) {
17560
17650
  const config = loadConfig(repoRoot);
17561
17651
  const worktreesDir = resolveWorktreesDir(repoRoot, config);
17562
17652
  acquireLock(repoRoot);
@@ -17584,7 +17674,7 @@ async function runDispatcher(repoRoot) {
17584
17674
  await handleCommand2(state, cmd);
17585
17675
  }
17586
17676
  await ingestInboxPlans(repoRoot, worktreesDir, config, state);
17587
- await processActivePlans(repoRoot, config, state, botLogin);
17677
+ await processActivePlans(repoRoot, config, state, botLogin, options2);
17588
17678
  saveState(repoRoot, state);
17589
17679
  await sleepUntilIpcOrTimeout(repoRoot, state.control_cursor, config.poll_interval_ms);
17590
17680
  } catch (error) {
@@ -17641,7 +17731,7 @@ function getFeedbackPollDecision(opts) {
17641
17731
  shouldUpdateLastPolledAt: !pollOnce && shouldPoll
17642
17732
  };
17643
17733
  }
17644
- async function processActivePlans(repoRoot, config, state, botLogin) {
17734
+ async function processActivePlans(repoRoot, config, state, botLogin, options2 = {}) {
17645
17735
  for (const [planId, ps] of Object.entries(state.plans)) {
17646
17736
  try {
17647
17737
  const planPath = join9(ps.worktree, ps.planRelpath);
@@ -17676,7 +17766,7 @@ async function processActivePlans(repoRoot, config, state, botLogin) {
17676
17766
  if (newFeedback.length > 0) {
17677
17767
  console.log(`\uD83D\uDCAC ${newFeedback.length} new feedback for ${planId}`);
17678
17768
  if (!isManualAgent) {
17679
- await runTriage(repoRoot, config, ps, plan, newFeedback);
17769
+ await runTriage(repoRoot, config, ps, plan, newFeedback, options2);
17680
17770
  }
17681
17771
  plan = parsePlan(planPath);
17682
17772
  const maxIds = getMaxFeedbackIds(newFeedback);
@@ -17699,7 +17789,15 @@ async function processActivePlans(repoRoot, config, state, botLogin) {
17699
17789
  const prompt = renderWorkerPrompt(repoRoot, plan, todo);
17700
17790
  const agentName = plan.frontmatter.agent ?? config.agents.default;
17701
17791
  const adapter = getAdapter(agentName);
17702
- await adapter.execute({ cwd: ps.worktree, prompt });
17792
+ const tmuxConfig = options2.tmux ? { sessionName: `prloom-${planId}` } : undefined;
17793
+ if (tmuxConfig) {
17794
+ ps.tmuxSession = tmuxConfig.sessionName;
17795
+ console.log(` [spawned in tmux session: ${tmuxConfig.sessionName}]`);
17796
+ }
17797
+ await adapter.execute({ cwd: ps.worktree, prompt, tmux: tmuxConfig });
17798
+ if (tmuxConfig) {
17799
+ ps.tmuxSession = undefined;
17800
+ }
17703
17801
  const committed = await commitAll(ps.worktree, `[prloom] ${planId}: TODO #${todo.index}`);
17704
17802
  if (committed) {
17705
17803
  await push(ps.worktree, ps.branch);
@@ -17747,13 +17845,14 @@ async function pollNewFeedback(repoRoot, ps, botLogin) {
17747
17845
  };
17748
17846
  return filterNewFeedback(allFeedback, cursors, botLogin);
17749
17847
  }
17750
- async function runTriage(repoRoot, config, ps, plan, feedback) {
17848
+ async function runTriage(repoRoot, config, ps, plan, feedback, options2 = {}) {
17751
17849
  ensureWorktreePrloomDir(ps.worktree);
17752
17850
  const triageAgent = config.agents.designer ?? config.agents.default;
17753
17851
  const adapter = getAdapter(triageAgent);
17754
17852
  const prompt = renderTriagePrompt(repoRoot, plan, feedback);
17755
17853
  console.log(`\uD83D\uDD0D Running triage for ${plan.frontmatter.id}...`);
17756
- await adapter.execute({ cwd: ps.worktree, prompt });
17854
+ const tmuxConfig = options2.tmux ? { sessionName: `prloom-triage-${plan.frontmatter.id}` } : undefined;
17855
+ await adapter.execute({ cwd: ps.worktree, prompt, tmux: tmuxConfig });
17757
17856
  try {
17758
17857
  const result = readTriageResultFile(ps.worktree);
17759
17858
  if (result.rebase_requested) {
@@ -17967,6 +18066,35 @@ var init_open = __esm(() => {
17967
18066
  init_adapters();
17968
18067
  });
17969
18068
 
18069
+ // src/cli/watch.ts
18070
+ var exports_watch = {};
18071
+ __export(exports_watch, {
18072
+ runWatch: () => runWatch
18073
+ });
18074
+ async function runWatch(repoRoot, planId) {
18075
+ const state = loadState(repoRoot);
18076
+ const ps = state.plans[planId];
18077
+ if (!ps) {
18078
+ console.error(`Plan not found in state: ${planId}`);
18079
+ console.error("Make sure the plan has been dispatched at least once.");
18080
+ process.exit(1);
18081
+ }
18082
+ if (!ps.tmuxSession) {
18083
+ console.error(`No active tmux session for ${planId}`);
18084
+ console.error("This plan may not be running, or dispatcher wasn't started with --tmux");
18085
+ process.exit(1);
18086
+ }
18087
+ console.log(`Attaching to ${ps.tmuxSession} (read-only)...`);
18088
+ console.log("Press Ctrl+B D to detach without interrupting the worker.");
18089
+ await execa("tmux", ["attach", "-t", ps.tmuxSession, "-r"], {
18090
+ stdio: "inherit"
18091
+ });
18092
+ }
18093
+ var init_watch = __esm(() => {
18094
+ init_execa();
18095
+ init_state();
18096
+ });
18097
+
17970
18098
  // src/cli/logs.ts
17971
18099
  var exports_logs = {};
17972
18100
  __export(exports_logs, {
@@ -23482,9 +23610,13 @@ yargs_default(hideBin(process.argv)).scriptName("prloom").usage("$0 <command> [o
23482
23610
  }), async (argv) => {
23483
23611
  const { runEdit: runEdit2 } = await Promise.resolve().then(() => (init_edit(), exports_edit));
23484
23612
  await runEdit2(await getRepoRoot(), argv["plan-id"], argv.agent, argv["no-designer"]);
23485
- }).command("start", "Start the dispatcher", () => {}, async () => {
23613
+ }).command("start", "Start the dispatcher", (yargs) => yargs.option("tmux", {
23614
+ type: "boolean",
23615
+ describe: "Run workers in tmux sessions for observation",
23616
+ default: false
23617
+ }), async (argv) => {
23486
23618
  const { runDispatcher: runDispatcher2 } = await Promise.resolve().then(() => (init_dispatcher(), exports_dispatcher));
23487
- await runDispatcher2(await getRepoRoot());
23619
+ await runDispatcher2(await getRepoRoot(), { tmux: argv.tmux });
23488
23620
  }).command("status", "Show plan states", () => {}, async () => {
23489
23621
  const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), exports_status));
23490
23622
  await runStatus2(await getRepoRoot());
@@ -23497,6 +23629,9 @@ yargs_default(hideBin(process.argv)).scriptName("prloom").usage("$0 <command> [o
23497
23629
  }).command("open <plan-id>", "Open TUI for manual work (requires paused)", (yargs) => yargs.positional("plan-id", { type: "string", demandOption: true }), async (argv) => {
23498
23630
  const { runOpen: runOpen2 } = await Promise.resolve().then(() => (init_open(), exports_open));
23499
23631
  await runOpen2(await getRepoRoot(), argv["plan-id"]);
23632
+ }).command("watch <plan-id>", "Observe a running worker (requires --tmux mode)", (yargs) => yargs.positional("plan-id", { type: "string", demandOption: true }), async (argv) => {
23633
+ const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), exports_watch));
23634
+ await runWatch2(await getRepoRoot(), argv["plan-id"]);
23500
23635
  }).command("logs <plan-id>", "Show session ID for a plan", (yargs) => yargs.positional("plan-id", { type: "string", demandOption: true }), async (argv) => {
23501
23636
  const { runLogs: runLogs2 } = await Promise.resolve().then(() => (init_logs(), exports_logs));
23502
23637
  await runLogs2(await getRepoRoot(), argv["plan-id"]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prloom",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "prloom": "./dist/cli/index.js"
@@ -1,22 +1,31 @@
1
1
  # Designer: Create a New Plan
2
2
 
3
- You are helping a software engineer **plan** a coding task for prloom.
3
+ You are helping a software engineer **plan** a coding task.
4
4
 
5
5
  > [!CAUTION] > **You are PLANNING, not building.**
6
6
  >
7
7
  > - Do NOT modify any code files
8
8
  > - The ONLY file you may edit is: `{{plan_path}}`
9
9
 
10
- ## Your Philosophy
10
+ ## Repository Context
11
+
12
+ You are working in:
13
+
14
+ - **Repo**: `{{repo_path}}`
15
+ - **Base branch**: `{{base_branch}}`
16
+ - **Worker agent**: `{{worker_agent}}`
11
17
 
12
- You are working with a technical user who knows what they want. Your job is to **extract requirements**, not to make decisions for them.
18
+ Explore the codebase freely to understand context. Do NOT ask questions you can answer by exploring (e.g., test commands, file locations, package.json contents).
13
19
 
14
- - **Ask clarifying questions** — do not assume
15
- - **Do NOT fill in gaps** — wait for the user to provide details
16
- - **Defer to the user's judgment** on design and implementation
17
- - Only fill in details if the user explicitly says "fill in the details" or similar
20
+ ## Your Philosophy
21
+
22
+ You are working with a technical user who knows what they want. Your job is to **extract requirements** and **discuss implementation**, not to make decisions for them.
18
23
 
19
- The user will give you information bit by bit. Your responsibility is to synthesize their requirements into a structured plan.
24
+ - **Explore the codebase** to understand context before asking questions
25
+ - **Ask clarifying questions** about user preferences and acceptance criteria
26
+ - **Discuss implementation approach** — do not jump straight to writing the plan
27
+ - **Defer to the user's judgment** on design decisions
28
+ - Only fill in the plan when the user confirms they're ready
20
29
 
21
30
  {{#if user_description}}
22
31
 
@@ -24,32 +33,48 @@ The user will give you information bit by bit. Your responsibility is to synthes
24
33
 
25
34
  > {{user_description}}
26
35
 
27
- Ask clarifying questions before filling in the plan. What files? What approach? Any constraints?
36
+ Explore relevant files, then discuss the implementation approach with the user before writing the plan.
28
37
  {{else}}
29
38
 
30
39
  ## Your First Step
31
40
 
32
41
  Ask the user: **What would you like to build?**
33
42
 
34
- Then ask follow-up questions:
43
+ Then:
35
44
 
36
- - How should it work?
37
- - What files are involved?
38
- - Any constraints or preferences?
39
- - What are the acceptance criteria?
40
- {{/if}}
45
+ 1. Explore the codebase to understand the relevant areas
46
+ 2. Discuss how to implement it (files to change, approach, trade-offs)
47
+ 3. Confirm acceptance criteria
48
+ 4. When the user is ready, write the plan
49
+ {{/if}}
41
50
 
42
51
  ## Plan Structure
43
52
 
44
- Once you have the requirements, fill in:
53
+ The plan file already has a template with these sections. Fill them in:
45
54
 
46
55
  - **Objective**: What will be built (1-2 sentences)
47
- - **Context**: Files to modify, test commands, constraints
48
- - **TODO**: Small, sequential tasks
56
+ - **Context**: Files to modify, constraints, any notes the Worker needs
57
+ - **TODO (Commits)**: Each item is ONE commit the Worker will make
58
+
59
+ ### TODO Rules
60
+
61
+ Each TODO item represents a **single commit**. Think of them as the git log you want to see:
62
+
63
+ ✅ Good TODOs (commits):
64
+
65
+ - `Fix PDF viewer to load beyond 10 pages`
66
+ - `Add horizontal resize handle to Learn panel`
67
+ - `Update LearnStore to trigger page prefetch`
68
+
69
+ ❌ Bad TODOs (not commits):
70
+
71
+ - `Run npm test` — this is verification, not a commit
72
+ - `Trace the code to understand X` — this is research, not a commit
73
+ - `Validate acceptance criteria` — this is testing, not a commit
49
74
 
50
- Leave the frontmatter alone.
75
+ The Worker will run tests and type-checks before each commit automatically.
51
76
 
52
- ## Runtime Context
77
+ ## Important
53
78
 
54
- - Base branch: `{{base_branch}}`
55
- - Worker agent: `{{worker_agent}}`
79
+ - The plan template already has a `## Progress Log` section — do NOT duplicate it
80
+ - Leave the frontmatter (`id`, `status`, `agent`, `base_branch`) alone