gwqadd 0.3.0 → 0.3.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
@@ -135,6 +135,11 @@ question. At that point the sentence, the repository name, its branch names and
135
135
  your modified file *paths* (never contents) go to that CLI. Nothing is created
136
136
  until you confirm.
137
137
 
138
+ The CLI is run in an **empty temporary directory**, not in your repository.
139
+ These tools are agents: given a repo they will read `CLAUDE.md`, your source and
140
+ your git log, and then name the branch after what they found instead of what you
141
+ asked for. Everything they should know is already in the prompt.
142
+
138
143
  None of this happens when you pass a branch name on the command line, or when
139
144
  there is no terminal. Scripts and agents keep the plain, silent contract.
140
145
 
package/bin/gwqadd.mjs CHANGED
@@ -2,7 +2,12 @@
2
2
  import { spawnSync, spawn } from 'node:child_process';
3
3
  import { parseArgs } from 'node:util';
4
4
  import { Buffer } from 'node:buffer';
5
- import { readFileSync, existsSync, readdirSync, renameSync, realpathSync } from 'node:fs';
5
+ import {
6
+ readFileSync, existsSync, readdirSync, renameSync, realpathSync,
7
+ mkdtempSync, rmSync,
8
+ } from 'node:fs';
9
+ import { tmpdir } from 'node:os';
10
+ import { join as joinPath } from 'node:path';
6
11
  import { fileURLToPath } from 'node:url';
7
12
  import { createInterface } from 'node:readline/promises';
8
13
 
@@ -627,6 +632,7 @@ function namingPrompt(ctx, description, rejected) {
627
632
  ? `Files already modified in the working tree, which is what the work touches:\n${ctx.dirty.map((f) => ` ${f}`).join('\n')}`
628
633
  : '',
629
634
  'Include the prefix and a slash. After it use lowercase ASCII words joined by hyphens. Under 40 characters. Be specific about this change, not generic.',
635
+ 'The description below is the only thing the branch is about. Do not name it after anything else you can see.',
630
636
  rejected.length
631
637
  ? `These were rejected — do not propose them or close variants:\n${rejected.map((r) => ` ${r}`).join('\n')}`
632
638
  : '',
@@ -662,15 +668,42 @@ function detectAi() {
662
668
  return AI_CLIS.find((c) => commandExists(c.bin)) ?? null;
663
669
  }
664
670
 
665
- // spawnSync would freeze the terminal for the 6-8 seconds these CLIs take to
666
- // boot, with no sign of life. Async plus an elapsed counter is the difference
671
+ // These CLIs are agents, not text transformers: run one inside a repository and
672
+ // it reads CLAUDE.md, the source and the git history, then names the branch
673
+ // after what it found instead of what the user asked for. Measured in this very
674
+ // repository — description "uiのバグの修正", three runs each:
675
+ //
676
+ // cwd = the repo feat/ui-bug-fix, fix/ui-display-bug, feat/ui-bug-fix
677
+ // cwd = empty dir fix/ui-display-bug, fix/ui-display-bug, fix/ui-display-bug
678
+ //
679
+ // In-repo it both wavered and picked `feat/` for a bug fix twice. It once
680
+ // answered `feat/naming-prompt-repo-context`, which is a phrase straight out of
681
+ // this repo's CLAUDE.md. Everything the model legitimately needs is already in
682
+ // the prompt, so it runs in an empty directory and is given nothing else.
683
+ //
684
+ // spawnSync would also freeze the terminal for the 6-8 seconds these CLIs take
685
+ // to boot, with no sign of life; async plus an elapsed counter is the difference
667
686
  // between "working" and "hung".
668
687
  function runAi(ai, prompt) {
669
688
  return new Promise((resolve) => {
689
+ let sandbox = '';
690
+ try {
691
+ sandbox = mkdtempSync(joinPath(tmpdir(), 'gwqadd-ai-'));
692
+ } catch { /* fall back to inheriting cwd rather than not answering at all */ }
693
+ const cleanup = () => {
694
+ if (!sandbox) return;
695
+ try { rmSync(sandbox, { recursive: true, force: true }); } catch { /* ignore */ }
696
+ sandbox = '';
697
+ };
698
+
670
699
  let child;
671
700
  try {
672
- child = spawn(ai.bin, [...ai.args, prompt], { stdio: ['ignore', 'pipe', 'pipe'] });
701
+ child = spawn(ai.bin, [...ai.args, prompt], {
702
+ stdio: ['ignore', 'pipe', 'pipe'],
703
+ ...(sandbox ? { cwd: sandbox } : {}),
704
+ });
673
705
  } catch (err) {
706
+ cleanup();
674
707
  return resolve({ ok: false, out: '', err: String(err?.message ?? err) });
675
708
  }
676
709
  let out = '';
@@ -680,10 +713,12 @@ function runAi(ai, prompt) {
680
713
  const kill = setTimeout(() => { try { child.kill('SIGKILL'); } catch { /* gone */ } }, 60_000);
681
714
  child.on('error', (e) => {
682
715
  clearTimeout(kill);
716
+ cleanup();
683
717
  resolve({ ok: false, out: '', err: String(e?.message ?? e) });
684
718
  });
685
719
  child.on('close', (code) => {
686
720
  clearTimeout(kill);
721
+ cleanup();
687
722
  resolve({ ok: code === 0, out, err });
688
723
  });
689
724
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gwqadd",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Create a branch and its gwq worktree in the repository you are in, and cd there.",
5
5
  "type": "module",
6
6
  "bin": {