npm-cli-gh-issue-preparator 1.2.0 → 1.4.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.
- package/CHANGELOG.md +19 -0
- package/bin/adapter/entry-points/cli/index.js +2 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/repositories/Xfce4TerminalCopilotRepository.js +27 -0
- package/bin/adapter/repositories/Xfce4TerminalCopilotRepository.js.map +1 -0
- package/bin/domain/usecases/StartPreparationUseCase.js +4 -1
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/adapter-interfaces/CopilotRepository.js +3 -0
- package/bin/domain/usecases/adapter-interfaces/CopilotRepository.js.map +1 -0
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +39 -0
- package/src/adapter/entry-points/cli/index.ts +3 -0
- package/src/adapter/repositories/Xfce4TerminalCopilotRepository.test.ts +143 -0
- package/src/adapter/repositories/Xfce4TerminalCopilotRepository.ts +32 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +57 -0
- package/src/domain/usecases/StartPreparationUseCase.ts +5 -1
- package/src/domain/usecases/adapter-interfaces/CopilotRepository.ts +3 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/repositories/Xfce4TerminalCopilotRepository.d.ts +6 -0
- package/types/adapter/repositories/Xfce4TerminalCopilotRepository.d.ts.map +1 -0
- package/types/domain/usecases/StartPreparationUseCase.d.ts +1 -0
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/CopilotRepository.d.ts +4 -0
- package/types/domain/usecases/adapter-interfaces/CopilotRepository.d.ts.map +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# [1.4.0](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/compare/v1.3.0...v1.4.0) (2026-01-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **cli:** add logFilePath to cli parameter ([8e7dba7](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/commit/8e7dba7608cac085883c7beaeca1cd5de490f46e))
|
|
7
|
+
|
|
8
|
+
# [1.3.0](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/compare/v1.2.0...v1.3.0) (2026-01-12)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* prevent command injection in xfce4-terminal spawn ([0a4e008](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/commit/0a4e008749b293cd1f1d311ad08adfc9f37033c3))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **src:** create CopilotRepository ([c79ed81](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/commit/c79ed8127342a5aa58fc2f83b10334bbb0fc6a0d)), closes [#38](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/issues/38)
|
|
19
|
+
|
|
1
20
|
# [1.2.0](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/compare/v1.1.0...v1.2.0) (2026-01-12)
|
|
2
21
|
|
|
3
22
|
|
|
@@ -25,6 +25,7 @@ program
|
|
|
25
25
|
.requiredOption('--awaitingWorkspaceStatus <status>', 'Status for issues awaiting workspace')
|
|
26
26
|
.requiredOption('--preparationStatus <status>', 'Status for issues in preparation')
|
|
27
27
|
.requiredOption('--defaultAgentName <name>', 'Default agent name')
|
|
28
|
+
.option('--logFilePath <path>', 'Path to log file')
|
|
28
29
|
.action(async (options) => {
|
|
29
30
|
const token = process.env.GH_TOKEN;
|
|
30
31
|
if (!token) {
|
|
@@ -40,6 +41,7 @@ program
|
|
|
40
41
|
awaitingWorkspaceStatus: options.awaitingWorkspaceStatus,
|
|
41
42
|
preparationStatus: options.preparationStatus,
|
|
42
43
|
defaultAgentName: options.defaultAgentName,
|
|
44
|
+
logFilePath: options.logFilePath,
|
|
43
45
|
});
|
|
44
46
|
});
|
|
45
47
|
program
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";;;;;;;AACA,oDAA4B;AAC5B,gBAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,yCAAoC;AACpC,8FAA2F;AAC3F,0HAAuH;AACvH,wFAAqF;AACrF,oFAAiF;AACjF,sFAAmF;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Xfce4TerminalCopilotRepository = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
class Xfce4TerminalCopilotRepository {
|
|
6
|
+
escapeForSingleQuotes(str) {
|
|
7
|
+
return str.replace(/'/g, "'\\''");
|
|
8
|
+
}
|
|
9
|
+
run(prompt, model, processTitle) {
|
|
10
|
+
const escapedPrompt = this.escapeForSingleQuotes(prompt);
|
|
11
|
+
const escapedTitle = this.escapeForSingleQuotes(processTitle);
|
|
12
|
+
const innerCommand = `copilot --model ${model} --allow-all-tools -p '${escapedPrompt}'`;
|
|
13
|
+
const escapedInnerCommand = this.escapeForSingleQuotes(innerCommand);
|
|
14
|
+
const title = `gh-issue-preparator: ${escapedTitle}`;
|
|
15
|
+
const child = (0, child_process_1.spawn)('xfce4-terminal', ['-T', title, '-e', `bash -c '${escapedInnerCommand}'`], {
|
|
16
|
+
detached: true,
|
|
17
|
+
stdio: 'ignore',
|
|
18
|
+
});
|
|
19
|
+
child.on('error', () => {
|
|
20
|
+
// Intentionally empty - fire-and-forget behavior
|
|
21
|
+
// Errors are silently ignored as this is a background terminal spawn
|
|
22
|
+
});
|
|
23
|
+
child.unref();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.Xfce4TerminalCopilotRepository = Xfce4TerminalCopilotRepository;
|
|
27
|
+
//# sourceMappingURL=Xfce4TerminalCopilotRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Xfce4TerminalCopilotRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/Xfce4TerminalCopilotRepository.ts"],"names":[],"mappings":";;;AACA,iDAAsC;AAEtC,MAAa,8BAA8B;IACjC,qBAAqB,CAAC,GAAW;QACvC,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,GAAG,CAAC,MAAc,EAAE,KAAmB,EAAE,YAAoB;QAC3D,MAAM,aAAa,GAAG,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,YAAY,GAAG,IAAI,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC;QAC9D,MAAM,YAAY,GAAG,mBAAmB,KAAK,0BAA0B,aAAa,GAAG,CAAC;QACxF,MAAM,mBAAmB,GAAG,IAAI,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC;QACrE,MAAM,KAAK,GAAG,wBAAwB,YAAY,EAAE,CAAC;QAErD,MAAM,KAAK,GAAG,IAAA,qBAAK,EACjB,gBAAgB,EAChB,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,mBAAmB,GAAG,CAAC,EACvD;YACE,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,QAAQ;SAChB,CACF,CAAC;QAEF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,iDAAiD;YACjD,qEAAqE;QACvE,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;CACF;AA5BD,wEA4BC"}
|
|
@@ -24,7 +24,10 @@ class StartPreparationUseCase {
|
|
|
24
24
|
.trim() || params.defaultAgentName;
|
|
25
25
|
issue.status = params.preparationStatus;
|
|
26
26
|
await this.issueRepository.update(issue, project);
|
|
27
|
-
|
|
27
|
+
const logFilePathArg = params.logFilePath
|
|
28
|
+
? `--logFilePath ${params.logFilePath}`
|
|
29
|
+
: '';
|
|
30
|
+
await this.localCommandRunner.runCommand(`aw ${issue.url} ${agent} ${project.url}${logFilePathArg ? ` ${logFilePathArg}` : ''}`);
|
|
28
31
|
}
|
|
29
32
|
};
|
|
30
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartPreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,uBAAuB;IAElC,YACmB,iBAAoC,EACpC,eAAgC,EAChC,kBAAsC;QAFtC,sBAAiB,GAAjB,iBAAiB,CAAmB;QACpC,oBAAe,GAAf,eAAe,CAAiB;QAChC,uBAAkB,GAAlB,kBAAkB,CAAoB;QAJzD,gCAA2B,GAAG,CAAC,CAAC;QAOhC,QAAG,GAAG,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"StartPreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,uBAAuB;IAElC,YACmB,iBAAoC,EACpC,eAAgC,EAChC,kBAAsC;QAFtC,sBAAiB,GAAjB,iBAAiB,CAAmB;QACpC,oBAAe,GAAf,eAAe,CAAiB;QAChC,uBAAkB,GAAlB,kBAAkB,CAAoB;QAJzD,gCAA2B,GAAG,CAAC,CAAC;QAOhC,QAAG,GAAG,KAAK,EAAE,MAMZ,EAAiB,EAAE;YAClB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAEzE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YAEnE,MAAM,uBAAuB,GAAG,SAAS,CAAC,MAAM,CAC9C,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,uBAAuB,CAC3D,CAAC;YACF,MAAM,4BAA4B,GAAG,SAAS,CAAC,MAAM,CACnD,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,iBAAiB,CACrD,CAAC,MAAM,CAAC;YAET,KACE,IAAI,CAAC,GAAG,4BAA4B,EACpC,CAAC;gBACD,IAAI,CAAC,GAAG,CACN,IAAI,CAAC,2BAA2B,EAChC,uBAAuB,CAAC,MAAM,GAAG,4BAA4B,CAC9D,EACD,CAAC,EAAE,EACH,CAAC;gBACD,MAAM,KAAK,GAAG,uBAAuB,CAAC,GAAG,EAAE,CAAC;gBAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,MAAM;gBACR,CAAC;gBACD,MAAM,KAAK,GACT,KAAK,CAAC,MAAM;qBACT,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;oBAC/C,EAAE,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;qBACzB,IAAI,EAAE,IAAI,MAAM,CAAC,gBAAgB,CAAC;gBACvC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,iBAAiB,CAAC;gBACxC,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBAElD,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW;oBACvC,CAAC,CAAC,iBAAiB,MAAM,CAAC,WAAW,EAAE;oBACvC,CAAC,CAAC,EAAE,CAAC;gBACP,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,CACtC,MAAM,KAAK,CAAC,GAAG,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACvF,CAAC;YACJ,CAAC;QACH,CAAC,CAAC;IAhDC,CAAC;CAiDL;AAvDD,0DAuDC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CopilotRepository.js","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/CopilotRepository.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -56,6 +56,45 @@ describe('CLI', () => {
|
|
|
56
56
|
awaitingWorkspaceStatus: 'Awaiting',
|
|
57
57
|
preparationStatus: 'Preparing',
|
|
58
58
|
defaultAgentName: 'agent1',
|
|
59
|
+
logFilePath: undefined,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should pass logFilePath to StartPreparationUseCase when provided', async () => {
|
|
64
|
+
const mockRun = jest.fn().mockResolvedValue(undefined);
|
|
65
|
+
const MockedStartPreparationUseCase = jest.mocked(StartPreparationUseCase);
|
|
66
|
+
|
|
67
|
+
MockedStartPreparationUseCase.mockImplementation(function (
|
|
68
|
+
this: StartPreparationUseCase,
|
|
69
|
+
) {
|
|
70
|
+
this.run = mockRun;
|
|
71
|
+
this.maximumPreparingIssuesCount = 6;
|
|
72
|
+
return this;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await program.parseAsync([
|
|
76
|
+
'node',
|
|
77
|
+
'test',
|
|
78
|
+
'startDaemon',
|
|
79
|
+
'--projectUrl',
|
|
80
|
+
'https://github.com/test/project',
|
|
81
|
+
'--awaitingWorkspaceStatus',
|
|
82
|
+
'Awaiting',
|
|
83
|
+
'--preparationStatus',
|
|
84
|
+
'Preparing',
|
|
85
|
+
'--defaultAgentName',
|
|
86
|
+
'agent1',
|
|
87
|
+
'--logFilePath',
|
|
88
|
+
'/path/to/log.txt',
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
expect(mockRun).toHaveBeenCalledTimes(1);
|
|
92
|
+
expect(mockRun).toHaveBeenCalledWith({
|
|
93
|
+
projectUrl: 'https://github.com/test/project',
|
|
94
|
+
awaitingWorkspaceStatus: 'Awaiting',
|
|
95
|
+
preparationStatus: 'Preparing',
|
|
96
|
+
defaultAgentName: 'agent1',
|
|
97
|
+
logFilePath: '/path/to/log.txt',
|
|
59
98
|
});
|
|
60
99
|
});
|
|
61
100
|
|
|
@@ -14,6 +14,7 @@ type StartDaemonOptions = {
|
|
|
14
14
|
awaitingWorkspaceStatus: string;
|
|
15
15
|
preparationStatus: string;
|
|
16
16
|
defaultAgentName: string;
|
|
17
|
+
logFilePath?: string;
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
type NotifyFinishedOptions = {
|
|
@@ -41,6 +42,7 @@ program
|
|
|
41
42
|
'Status for issues in preparation',
|
|
42
43
|
)
|
|
43
44
|
.requiredOption('--defaultAgentName <name>', 'Default agent name')
|
|
45
|
+
.option('--logFilePath <path>', 'Path to log file')
|
|
44
46
|
.action(async (options: StartDaemonOptions) => {
|
|
45
47
|
const token = process.env.GH_TOKEN;
|
|
46
48
|
if (!token) {
|
|
@@ -63,6 +65,7 @@ program
|
|
|
63
65
|
awaitingWorkspaceStatus: options.awaitingWorkspaceStatus,
|
|
64
66
|
preparationStatus: options.preparationStatus,
|
|
65
67
|
defaultAgentName: options.defaultAgentName,
|
|
68
|
+
logFilePath: options.logFilePath,
|
|
66
69
|
});
|
|
67
70
|
});
|
|
68
71
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const mockUnref = jest.fn();
|
|
2
|
+
const mockOn = jest.fn();
|
|
3
|
+
const mockSpawn = jest.fn(() => ({ on: mockOn, unref: mockUnref }));
|
|
4
|
+
|
|
5
|
+
jest.mock('child_process', () => ({
|
|
6
|
+
spawn: mockSpawn,
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { Xfce4TerminalCopilotRepository } from './Xfce4TerminalCopilotRepository';
|
|
10
|
+
|
|
11
|
+
describe('Xfce4TerminalCopilotRepository', () => {
|
|
12
|
+
let repository: Xfce4TerminalCopilotRepository;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
repository = new Xfce4TerminalCopilotRepository();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('run', () => {
|
|
20
|
+
it('should spawn xfce4-terminal with correct arguments', () => {
|
|
21
|
+
repository.run('test prompt', 'gpt-5-mini', 'test-process');
|
|
22
|
+
|
|
23
|
+
expect(mockSpawn).toHaveBeenCalledWith(
|
|
24
|
+
'xfce4-terminal',
|
|
25
|
+
[
|
|
26
|
+
'-T',
|
|
27
|
+
'gh-issue-preparator: test-process',
|
|
28
|
+
'-e',
|
|
29
|
+
"bash -c 'copilot --model gpt-5-mini --allow-all-tools -p '\\''test prompt'\\'''",
|
|
30
|
+
],
|
|
31
|
+
{
|
|
32
|
+
detached: true,
|
|
33
|
+
stdio: 'ignore',
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
expect(mockOn).toHaveBeenCalledWith('error', expect.any(Function));
|
|
37
|
+
expect(mockUnref).toHaveBeenCalled();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should escape single quotes in prompt to prevent command injection', () => {
|
|
41
|
+
repository.run(
|
|
42
|
+
"prompt with 'single quotes'",
|
|
43
|
+
'gpt-5-mini',
|
|
44
|
+
'test-process',
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(mockSpawn).toHaveBeenCalledWith(
|
|
48
|
+
'xfce4-terminal',
|
|
49
|
+
[
|
|
50
|
+
'-T',
|
|
51
|
+
'gh-issue-preparator: test-process',
|
|
52
|
+
'-e',
|
|
53
|
+
"bash -c 'copilot --model gpt-5-mini --allow-all-tools -p '\\''prompt with '\\''\\'\\'''\\''single quotes'\\''\\'\\'''\\'''\\'''",
|
|
54
|
+
],
|
|
55
|
+
{
|
|
56
|
+
detached: true,
|
|
57
|
+
stdio: 'ignore',
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should escape backticks in prompt to prevent command injection', () => {
|
|
63
|
+
repository.run('prompt with `backticks`', 'gpt-5-mini', 'test-process');
|
|
64
|
+
|
|
65
|
+
expect(mockSpawn).toHaveBeenCalledWith(
|
|
66
|
+
'xfce4-terminal',
|
|
67
|
+
[
|
|
68
|
+
'-T',
|
|
69
|
+
'gh-issue-preparator: test-process',
|
|
70
|
+
'-e',
|
|
71
|
+
"bash -c 'copilot --model gpt-5-mini --allow-all-tools -p '\\''prompt with `backticks`'\\'''",
|
|
72
|
+
],
|
|
73
|
+
{
|
|
74
|
+
detached: true,
|
|
75
|
+
stdio: 'ignore',
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should escape command substitution in prompt to prevent injection', () => {
|
|
81
|
+
repository.run('prompt with $(whoami)', 'gpt-5-mini', 'test-process');
|
|
82
|
+
|
|
83
|
+
expect(mockSpawn).toHaveBeenCalledWith(
|
|
84
|
+
'xfce4-terminal',
|
|
85
|
+
[
|
|
86
|
+
'-T',
|
|
87
|
+
'gh-issue-preparator: test-process',
|
|
88
|
+
'-e',
|
|
89
|
+
"bash -c 'copilot --model gpt-5-mini --allow-all-tools -p '\\''prompt with $(whoami)'\\'''",
|
|
90
|
+
],
|
|
91
|
+
{
|
|
92
|
+
detached: true,
|
|
93
|
+
stdio: 'ignore',
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should escape single quotes in processTitle', () => {
|
|
99
|
+
repository.run('prompt', 'gpt-5-mini', "title with 'quotes'");
|
|
100
|
+
|
|
101
|
+
expect(mockSpawn).toHaveBeenCalledWith(
|
|
102
|
+
'xfce4-terminal',
|
|
103
|
+
[
|
|
104
|
+
'-T',
|
|
105
|
+
"gh-issue-preparator: title with '\\''quotes'\\''",
|
|
106
|
+
'-e',
|
|
107
|
+
"bash -c 'copilot --model gpt-5-mini --allow-all-tools -p '\\''prompt'\\'''",
|
|
108
|
+
],
|
|
109
|
+
{
|
|
110
|
+
detached: true,
|
|
111
|
+
stdio: 'ignore',
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should set correct window title', () => {
|
|
117
|
+
repository.run('prompt', 'gpt-5-mini', 'my-custom-title');
|
|
118
|
+
|
|
119
|
+
expect(mockSpawn).toHaveBeenCalledWith(
|
|
120
|
+
'xfce4-terminal',
|
|
121
|
+
expect.arrayContaining(['-T', 'gh-issue-preparator: my-custom-title']),
|
|
122
|
+
expect.any(Object),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should register error handler and silently ignore errors', () => {
|
|
127
|
+
let capturedHandler: ((error: Error) => void) | undefined;
|
|
128
|
+
mockOn.mockImplementation(
|
|
129
|
+
(event: string, handler: (error: Error) => void) => {
|
|
130
|
+
if (event === 'error') {
|
|
131
|
+
capturedHandler = handler;
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
repository.run('test', 'gpt-5-mini', 'test');
|
|
137
|
+
|
|
138
|
+
expect(mockOn).toHaveBeenCalledWith('error', expect.any(Function));
|
|
139
|
+
expect(capturedHandler).toBeDefined();
|
|
140
|
+
expect(() => capturedHandler?.(new Error('spawn failed'))).not.toThrow();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CopilotRepository } from '../../domain/usecases/adapter-interfaces/CopilotRepository';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
|
|
4
|
+
export class Xfce4TerminalCopilotRepository implements CopilotRepository {
|
|
5
|
+
private escapeForSingleQuotes(str: string): string {
|
|
6
|
+
return str.replace(/'/g, "'\\''");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
run(prompt: string, model: 'gpt-5-mini', processTitle: string): void {
|
|
10
|
+
const escapedPrompt = this.escapeForSingleQuotes(prompt);
|
|
11
|
+
const escapedTitle = this.escapeForSingleQuotes(processTitle);
|
|
12
|
+
const innerCommand = `copilot --model ${model} --allow-all-tools -p '${escapedPrompt}'`;
|
|
13
|
+
const escapedInnerCommand = this.escapeForSingleQuotes(innerCommand);
|
|
14
|
+
const title = `gh-issue-preparator: ${escapedTitle}`;
|
|
15
|
+
|
|
16
|
+
const child = spawn(
|
|
17
|
+
'xfce4-terminal',
|
|
18
|
+
['-T', title, '-e', `bash -c '${escapedInnerCommand}'`],
|
|
19
|
+
{
|
|
20
|
+
detached: true,
|
|
21
|
+
stdio: 'ignore',
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
child.on('error', () => {
|
|
26
|
+
// Intentionally empty - fire-and-forget behavior
|
|
27
|
+
// Errors are silently ignored as this is a background terminal spawn
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
child.unref();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -142,6 +142,63 @@ describe('StartPreparationUseCase', () => {
|
|
|
142
142
|
expect(issue7UpdateCalls).toHaveLength(0);
|
|
143
143
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
144
144
|
});
|
|
145
|
+
it('should append logFilePath to aw command when provided', async () => {
|
|
146
|
+
const awaitingIssues: Issue[] = [
|
|
147
|
+
{
|
|
148
|
+
id: '1',
|
|
149
|
+
url: 'url1',
|
|
150
|
+
title: 'Issue 1',
|
|
151
|
+
labels: ['category:impl'],
|
|
152
|
+
status: 'Awaiting Workspace',
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
156
|
+
mockIssueRepository.getAllOpened.mockResolvedValueOnce(awaitingIssues);
|
|
157
|
+
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
158
|
+
stdout: '',
|
|
159
|
+
stderr: '',
|
|
160
|
+
exitCode: 0,
|
|
161
|
+
});
|
|
162
|
+
await useCase.run({
|
|
163
|
+
projectUrl: 'https://github.com/user/repo',
|
|
164
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
165
|
+
preparationStatus: 'Preparation',
|
|
166
|
+
defaultAgentName: 'agent1',
|
|
167
|
+
logFilePath: '/path/to/log.txt',
|
|
168
|
+
});
|
|
169
|
+
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
170
|
+
expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe(
|
|
171
|
+
'aw url1 impl https://github.com/user/repo --logFilePath /path/to/log.txt',
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
it('should not append logFilePath to aw command when not provided', async () => {
|
|
175
|
+
const awaitingIssues: Issue[] = [
|
|
176
|
+
{
|
|
177
|
+
id: '1',
|
|
178
|
+
url: 'url1',
|
|
179
|
+
title: 'Issue 1',
|
|
180
|
+
labels: ['category:impl'],
|
|
181
|
+
status: 'Awaiting Workspace',
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
185
|
+
mockIssueRepository.getAllOpened.mockResolvedValueOnce(awaitingIssues);
|
|
186
|
+
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
187
|
+
stdout: '',
|
|
188
|
+
stderr: '',
|
|
189
|
+
exitCode: 0,
|
|
190
|
+
});
|
|
191
|
+
await useCase.run({
|
|
192
|
+
projectUrl: 'https://github.com/user/repo',
|
|
193
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
194
|
+
preparationStatus: 'Preparation',
|
|
195
|
+
defaultAgentName: 'agent1',
|
|
196
|
+
});
|
|
197
|
+
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
198
|
+
expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe(
|
|
199
|
+
'aw url1 impl https://github.com/user/repo',
|
|
200
|
+
);
|
|
201
|
+
});
|
|
145
202
|
it('should handle defensive break when pop returns undefined', async () => {
|
|
146
203
|
const awaitingIssue: Issue = {
|
|
147
204
|
id: '1',
|
|
@@ -15,6 +15,7 @@ export class StartPreparationUseCase {
|
|
|
15
15
|
awaitingWorkspaceStatus: string;
|
|
16
16
|
preparationStatus: string;
|
|
17
17
|
defaultAgentName: string;
|
|
18
|
+
logFilePath?: string;
|
|
18
19
|
}): Promise<void> => {
|
|
19
20
|
const project = await this.projectRepository.getByUrl(params.projectUrl);
|
|
20
21
|
|
|
@@ -48,8 +49,11 @@ export class StartPreparationUseCase {
|
|
|
48
49
|
issue.status = params.preparationStatus;
|
|
49
50
|
await this.issueRepository.update(issue, project);
|
|
50
51
|
|
|
52
|
+
const logFilePathArg = params.logFilePath
|
|
53
|
+
? `--logFilePath ${params.logFilePath}`
|
|
54
|
+
: '';
|
|
51
55
|
await this.localCommandRunner.runCommand(
|
|
52
|
-
`aw ${issue.url} ${agent} ${project.url}`,
|
|
56
|
+
`aw ${issue.url} ${agent} ${project.url}${logFilePathArg ? ` ${logFilePathArg}` : ''}`,
|
|
53
57
|
);
|
|
54
58
|
}
|
|
55
59
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAsF9B,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CopilotRepository } from '../../domain/usecases/adapter-interfaces/CopilotRepository';
|
|
2
|
+
export declare class Xfce4TerminalCopilotRepository implements CopilotRepository {
|
|
3
|
+
private escapeForSingleQuotes;
|
|
4
|
+
run(prompt: string, model: 'gpt-5-mini', processTitle: string): void;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=Xfce4TerminalCopilotRepository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Xfce4TerminalCopilotRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/Xfce4TerminalCopilotRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAG/F,qBAAa,8BAA+B,YAAW,iBAAiB;IACtE,OAAO,CAAC,qBAAqB;IAI7B,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;CAuBrE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAE7E,qBAAa,uBAAuB;IAGhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IAJrC,2BAA2B,SAAK;gBAEb,iBAAiB,EAAE,iBAAiB,EACpC,eAAe,EAAE,eAAe,EAChC,kBAAkB,EAAE,kBAAkB;IAGzD,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,uBAAuB,EAAE,MAAM,CAAC;QAChC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAE7E,qBAAa,uBAAuB;IAGhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IAJrC,2BAA2B,SAAK;gBAEb,iBAAiB,EAAE,iBAAiB,EACpC,eAAe,EAAE,eAAe,EAChC,kBAAkB,EAAE,kBAAkB;IAGzD,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,uBAAuB,EAAE,MAAM,CAAC;QAChC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,KAAG,OAAO,CAAC,IAAI,CAAC,CAwCf;CACH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CopilotRepository.d.ts","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/CopilotRepository.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtE"}
|