@capstone-nomos/cli 0.1.2 → 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.
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { rmSync } from 'node:fs';
|
|
2
|
+
// 뒷정리용 삭제. Windows에서는 자식 프로세스(claude·vitest)가 끝난 직후에도 폴더가 잠깐 잠겨 있어 EBUSY가 난다.
|
|
3
|
+
// Node의 재시도 옵션으로 잠시 기다렸다 다시 지우고, 그래도 못 지우면 경고만 남기고 넘어간다 —
|
|
4
|
+
// finally에서 던지면 이미 받아 둔 결과(PM 초안·검증 보고)를 오류가 덮는다(pm-worker에서 실제로 그랬다).
|
|
5
|
+
export function removeQuietly(dir, warn = defaultWarn) {
|
|
6
|
+
try {
|
|
7
|
+
rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
warn(`임시 폴더를 지우지 못했습니다(남겨 둠): ${dir} — ${err.message}`);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// stdout은 MCP 서버에서 JSON-RPC 전용이라 경고는 stderr로 쓴다.
|
|
16
|
+
function defaultWarn(line) {
|
|
17
|
+
process.stderr.write(`[nomos] 경고: ${line}\n`);
|
|
18
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { mkdtempSync,
|
|
2
|
+
import { mkdtempSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { resolveClaudeCommand } from './runner.js';
|
|
6
|
+
import { removeQuietly } from './cleanup.js';
|
|
6
7
|
// 지침·프롬프트는 길어서(이전 초안이 붙는 수정 요청) 명령줄로 넘기지 않는다 — Windows 명령줄은 32k자에서 잘린다.
|
|
7
8
|
// 지침은 파일, 프롬프트는 stdin. 도구는 전부 끄고(--tools ""), 사용자 MCP·세션 기록도 쓰지 않는다.
|
|
8
9
|
export function buildPmClaudeArgs(job, systemPromptFile) {
|
|
@@ -80,7 +81,7 @@ export async function runPmJob(job, run = spawnClaude) {
|
|
|
80
81
|
return toJobResult(out, job.request.model);
|
|
81
82
|
}
|
|
82
83
|
finally {
|
|
83
|
-
|
|
84
|
+
removeQuietly(dir);
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
// 작업 하나를 가져와 처리한다. 처리했으면 true.
|
package/dist/executor/prompt.js
CHANGED
|
@@ -12,6 +12,14 @@ export function buildTaskPrompt(briefing, branch) {
|
|
|
12
12
|
if (spec) {
|
|
13
13
|
sections.push(`# 명세 ${spec.featureKey} — ${spec.title}\n${spec.content}`);
|
|
14
14
|
}
|
|
15
|
+
// 직전 제출이 반려됐다 — 같은 태스크 브랜치에 이전 작업이 남아 있으니 이어서 고친다.
|
|
16
|
+
if (briefing.lastRejection) {
|
|
17
|
+
sections.push([
|
|
18
|
+
'# 직전 제출이 대표에게 반려됐다',
|
|
19
|
+
`사유: ${briefing.lastRejection.reason}`,
|
|
20
|
+
`반려된 커밋: ${briefing.lastRejection.commitSha ?? '(알 수 없음)'} — 이 브랜치에 이전 작업이 남아 있다. 처음부터 다시 만들지 말고 사유에 맞게 고친 뒤 다시 제출한다.`,
|
|
21
|
+
].join('\n'));
|
|
22
|
+
}
|
|
15
23
|
if (briefing.notesBlock) {
|
|
16
24
|
sections.push(briefing.notesBlock);
|
|
17
25
|
}
|
package/dist/executor/verify.js
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
// 여기서 판정하는 것은 "명령이 0으로 끝났는가"뿐이다. 출력을 읽고 해석하지 않는다 —
|
|
6
6
|
// 해석이 들어가는 순간 브릿지마다 결과가 달라지고, 그건 결정적 검증이 아니다.
|
|
7
7
|
import { execFile } from 'node:child_process';
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { promisify } from 'node:util';
|
|
11
|
+
import { removeQuietly } from './cleanup.js';
|
|
11
12
|
const run = promisify(execFile);
|
|
12
13
|
// 린트와 테스트는 오래 걸릴 수 있지만 무한정은 아니다. 초과하면 SKIPPED로 남긴다 —
|
|
13
14
|
// FAIL로 적으면 코드가 아니라 기계가 느려서 재시도 횟수를 잃는다.
|
|
@@ -80,7 +81,7 @@ export async function runSpecTests(workspaceDir, specTests) {
|
|
|
80
81
|
}
|
|
81
82
|
const dir = path.join(workspaceDir, SPEC_TEST_DIR);
|
|
82
83
|
// 시험지는 작업 결과가 아니다. 커밋에 섞이면 V3에서 신고 누락으로 잡힌다.
|
|
83
|
-
|
|
84
|
+
removeQuietly(dir);
|
|
84
85
|
mkdirSync(dir, { recursive: true });
|
|
85
86
|
try {
|
|
86
87
|
const files = specTests.map((t) => {
|
|
@@ -101,6 +102,6 @@ export async function runSpecTests(workspaceDir, specTests) {
|
|
|
101
102
|
}
|
|
102
103
|
finally {
|
|
103
104
|
// 남겨두면 다음 실행에서 모델이 시험지를 읽고 거기에 맞춰 고칠 수 있다.
|
|
104
|
-
|
|
105
|
+
removeQuietly(dir);
|
|
105
106
|
}
|
|
106
107
|
}
|
|
@@ -25,9 +25,12 @@ function git(repoPath, args) {
|
|
|
25
25
|
return execFileSync('git', args, { cwd: repoPath, encoding: 'utf-8' }).trim();
|
|
26
26
|
}
|
|
27
27
|
function branchExists(repoPath, branch) {
|
|
28
|
+
return refExists(repoPath, `refs/heads/${branch}`);
|
|
29
|
+
}
|
|
30
|
+
function refExists(repoPath, ref) {
|
|
28
31
|
try {
|
|
29
32
|
// stdio를 삼킨다 — 없는 브랜치면 git이 stderr에 fatal을 찍어 로그를 더럽힌다.
|
|
30
|
-
execFileSync('git', ['rev-parse', '--verify',
|
|
33
|
+
execFileSync('git', ['rev-parse', '--verify', '--quiet', ref], {
|
|
31
34
|
cwd: repoPath,
|
|
32
35
|
stdio: 'ignore',
|
|
33
36
|
});
|
|
@@ -46,9 +49,14 @@ export function prepareWorkspace(input) {
|
|
|
46
49
|
excludeNomosFiles(input.repoPath);
|
|
47
50
|
if (!existsSync(dir)) {
|
|
48
51
|
mkdirSync(path.dirname(dir), { recursive: true });
|
|
52
|
+
// 재시도(검증 실패·반려)는 같은 태스크 브랜치에서 **이어서** 고친다. 이 노트북에 브랜치가 없어도(다른 노트북에서 했거나 새로 받은 클론)
|
|
53
|
+
// origin에 있으면 거기서 이어 간다 — 기본 브랜치에서 새로 따면 이전 작업이 안 보이고, 제출 때 push가 갈라진 이력으로 거부된다.
|
|
54
|
+
const remote = `refs/remotes/origin/${branch}`;
|
|
49
55
|
const args = existed
|
|
50
56
|
? ['worktree', 'add', dir, branch]
|
|
51
|
-
:
|
|
57
|
+
: refExists(input.repoPath, remote)
|
|
58
|
+
? ['worktree', 'add', '-b', branch, dir, `origin/${branch}`]
|
|
59
|
+
: ['worktree', 'add', '-b', branch, dir, input.baseBranch];
|
|
52
60
|
git(input.repoPath, args);
|
|
53
61
|
}
|
|
54
62
|
const claudeDir = path.join(dir, '.claude');
|
|
@@ -105,7 +113,8 @@ export function cleanWorkspaces() {
|
|
|
105
113
|
const root = workspacesRoot();
|
|
106
114
|
if (!existsSync(root))
|
|
107
115
|
return { pruned, removed: null };
|
|
108
|
-
|
|
116
|
+
// 사람이 직접 부르는 명령이라 못 지우면 오류를 그대로 보인다(삼키면 지웠다고 거짓 보고한다). 잠금만 재시도로 넘긴다.
|
|
117
|
+
rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
|
109
118
|
return { pruned, removed: root };
|
|
110
119
|
}
|
|
111
120
|
export function headSha(dir) {
|