sentix 2.1.0 → 2.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentix",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Autonomous multi-agent DevSecOps pipeline CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -504,8 +504,16 @@ function generateGovernorDirective() {
504
504
  # Sentix Governor — 필수 준수 사항
505
505
 
506
506
  > **이 프로젝트는 Sentix 프레임워크로 관리된다.**
507
- > **아래 규칙은 어떤 역할(/frontend, /backend 등)에서든 반드시 따라야 한다.**
508
- > 상세 설계: FRAMEWORK.md, 세부 규칙: docs/
507
+ > **아래 규칙은 어떤 역할(/frontend, /backend 등)에서든, 어떤 worktree에서든 반드시 따라야 한다.**
508
+
509
+ ## 세션 시작 시 필수 읽기 (순서대로)
510
+
511
+ \`\`\`
512
+ 1. CLAUDE.md (이 파일)
513
+ 2. FRAMEWORK.md — 5-Layer 아키텍처, 에이전트 정의
514
+ 3. docs/agent-methods.md — 에이전트별 메서드 순서 (필수 준수)
515
+ 4. .sentix/rules/hard-rules.md — 파괴 방지 6개 규칙
516
+ \`\`\`
509
517
 
510
518
  ## 코드 수정 전 필수 절차
511
519
 
@@ -517,6 +525,22 @@ function generateGovernorDirective() {
517
525
  4. 티켓 없이 코드 수정 금지 — sentix ticket create 또는 sentix feature add 사용
518
526
  \`\`\`
519
527
 
528
+ ## 에이전트 메서드 순서 (docs/agent-methods.md 필수 참조)
529
+
530
+ \`\`\`
531
+ planner: analyze() → research() → scope() → estimate() → emit()
532
+ → WHAT/WHERE만 정의. HOW(구현 방법) 금지.
533
+
534
+ dev: snapshot() → implement() → test() → verify() → report()
535
+ → 구현 방법은 dev가 결정. 품질 판단은 pr-review에 위임.
536
+
537
+ pr-review: diff() → validate() → grade() → calibrate() → verdict()
538
+ → 회의적 판정. 의심스러우면 REJECTED.
539
+
540
+ dev-fix: diagnose() → fix() → test() → learn() → report()
541
+ → LESSON_LEARNED 필수.
542
+ \`\`\`
543
+
520
544
  ## 파괴 방지 하드 룰 6개
521
545
 
522
546
  1. 작업 전 테스트 스냅샷 필수
@@ -549,7 +573,7 @@ sentix ticket create "설명" # 버그 티켓 생성
549
573
  sentix feature add "설명" # 기능 티켓 생성
550
574
  sentix status # 상태 확인
551
575
  sentix doctor # 설치 진단
552
- sentix update # 프레임워크 최신화
576
+ sentix update # 프레임워크 최신화 (worktree도 root 포함)
553
577
  \`\`\`
554
578
  `;
555
579
  }
@@ -11,8 +11,9 @@
11
11
 
12
12
  import { registerCommand } from '../registry.js';
13
13
  import { VERSION } from '../version.js';
14
- import { readFileSync, existsSync } from 'node:fs';
14
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
15
15
  import { resolve, dirname } from 'node:path';
16
+ import { execSync } from 'node:child_process';
16
17
  import { fileURLToPath } from 'node:url';
17
18
 
18
19
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -45,6 +46,104 @@ const SYNC_FILES = [
45
46
  { src: '.claude/agents/security.md', dst: '.claude/agents/security.md' },
46
47
  ];
47
48
 
49
+ // ── Worktree 감지: 현재 위치 + main working tree ────────
50
+
51
+ function getUpdateTargets(ctx) {
52
+ const cwd = resolve(ctx.cwd);
53
+ const targets = [cwd];
54
+
55
+ try {
56
+ // git worktree인지 확인
57
+ const gitCommonDir = execSync('git rev-parse --git-common-dir', {
58
+ cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
59
+ }).trim();
60
+
61
+ const mainGitDir = resolve(cwd, gitCommonDir);
62
+
63
+ // main working tree 경로 찾기
64
+ const worktreeList = execSync('git worktree list --porcelain', {
65
+ cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
66
+ });
67
+
68
+ const mainMatch = worktreeList.match(/^worktree (.+)$/m);
69
+ if (mainMatch) {
70
+ const mainRoot = mainMatch[1].trim();
71
+ const resolvedMain = resolve(mainRoot);
72
+ if (resolvedMain !== cwd && existsSync(resolvedMain)) {
73
+ targets.push(resolvedMain);
74
+ ctx.log(`Worktree detected → also updating main: ${resolvedMain}`);
75
+ }
76
+ }
77
+ } catch {
78
+ // git 없거나 worktree 아님 — 현재 디렉토리만
79
+ }
80
+
81
+ return targets;
82
+ }
83
+
84
+ // ── 파일 동기화 실행 ─────────────────────────────────────
85
+
86
+ async function syncFiles(targetDir, dryRun, ctx) {
87
+ const results = { updated: [], created: [], skipped: [], unchanged: [] };
88
+
89
+ for (const { src, dst } of SYNC_FILES) {
90
+ const srcPath = resolve(sentixRoot, src);
91
+ const dstPath = resolve(targetDir, dst);
92
+
93
+ if (!existsSync(srcPath)) {
94
+ results.skipped.push({ file: dst, reason: 'source not found' });
95
+ continue;
96
+ }
97
+
98
+ const srcContent = readFileSync(srcPath, 'utf-8');
99
+
100
+ if (existsSync(dstPath)) {
101
+ const dstContent = readFileSync(dstPath, 'utf-8');
102
+
103
+ if (srcContent === dstContent) {
104
+ results.unchanged.push(dst);
105
+ continue;
106
+ }
107
+
108
+ const srcLines = srcContent.split('\n');
109
+ const dstLines = dstContent.split('\n');
110
+ const added = srcLines.length - dstLines.length;
111
+
112
+ ctx.log(`${dryRun ? '[DRY] ' : ''}Updating: ${dst}`);
113
+ ctx.log(` ${dstLines.length} lines → ${srcLines.length} lines (${added >= 0 ? '+' : ''}${added})`);
114
+
115
+ if (!dryRun) {
116
+ mkdirSync(dirname(dstPath), { recursive: true });
117
+ writeFileSync(dstPath, srcContent);
118
+ ctx.success(`Updated: ${dst}`);
119
+ }
120
+ results.updated.push(dst);
121
+ } else {
122
+ ctx.log(`${dryRun ? '[DRY] ' : ''}Creating: ${dst}`);
123
+ if (!dryRun) {
124
+ mkdirSync(dirname(dstPath), { recursive: true });
125
+ writeFileSync(dstPath, srcContent);
126
+ ctx.success(`Created: ${dst}`);
127
+ }
128
+ results.created.push(dst);
129
+ }
130
+ }
131
+
132
+ // 요약
133
+ const totalChanges = results.updated.length + results.created.length;
134
+ if (results.updated.length > 0) {
135
+ ctx.log(`Updated: ${results.updated.length} — ${results.updated.join(', ')}`);
136
+ }
137
+ if (results.created.length > 0) {
138
+ ctx.log(`Created: ${results.created.length} — ${results.created.join(', ')}`);
139
+ }
140
+ if (totalChanges === 0) {
141
+ ctx.success('Already up to date.');
142
+ } else if (!dryRun) {
143
+ ctx.success(`${totalChanges} file(s) updated to sentix v${VERSION}.`);
144
+ }
145
+ }
146
+
48
147
  registerCommand('update', {
49
148
  description: 'Update framework files to the latest sentix version',
50
149
  usage: 'sentix update [--dry]',
@@ -54,9 +153,6 @@ registerCommand('update', {
54
153
 
55
154
  ctx.log(`sentix update v${VERSION}`);
56
155
  ctx.log(`source: ${sentixRoot}`);
57
- ctx.log(`target: ${ctx.cwd}`);
58
- if (dryRun) ctx.warn('DRY RUN — no files will be changed\n');
59
- else ctx.log('');
60
156
 
61
157
  // sentix 원본에서 실행 중인지 확인
62
158
  if (resolve(ctx.cwd) === resolve(sentixRoot)) {
@@ -64,95 +160,22 @@ registerCommand('update', {
64
160
  return;
65
161
  }
66
162
 
67
- // sentix가 초기화된 프로젝트인지 확인
68
- if (!ctx.exists('.sentix/config.toml') && !ctx.exists('CLAUDE.md')) {
69
- ctx.error('This project has not been initialized with sentix.');
70
- ctx.log('Run: sentix init');
71
- return;
72
- }
73
-
74
- const results = { updated: [], created: [], skipped: [], unchanged: [] };
163
+ // 업데이트 대상 디렉토리 수집 (현재 + worktree면 root도)
164
+ const targets = getUpdateTargets(ctx);
75
165
 
76
- for (const { src, dst } of SYNC_FILES) {
77
- const srcPath = resolve(sentixRoot, src);
166
+ for (const target of targets) {
167
+ ctx.log(`\n--- target: ${target} ---`);
168
+ if (dryRun) ctx.warn('DRY RUN\n');
78
169
 
79
- // 원본 파일이 없으면 스킵
80
- if (!existsSync(srcPath)) {
81
- results.skipped.push({ file: dst, reason: 'source not found' });
170
+ // sentix가 초기화된 프로젝트인지 확인
171
+ const hasConfig = existsSync(resolve(target, '.sentix/config.toml'));
172
+ const hasClaude = existsSync(resolve(target, 'CLAUDE.md'));
173
+ if (!hasConfig && !hasClaude) {
174
+ ctx.warn(`Not a sentix project: ${target} — skipping`);
82
175
  continue;
83
176
  }
84
177
 
85
- const srcContent = readFileSync(srcPath, 'utf-8');
86
-
87
- if (ctx.exists(dst)) {
88
- const dstContent = await ctx.readFile(dst);
89
-
90
- if (srcContent === dstContent) {
91
- results.unchanged.push(dst);
92
- continue;
93
- }
94
-
95
- // diff 요약 생성
96
- const srcLines = srcContent.split('\n');
97
- const dstLines = dstContent.split('\n');
98
- const added = srcLines.length - dstLines.length;
99
-
100
- ctx.log(`${dryRun ? '[DRY] ' : ''}Updating: ${dst}`);
101
- ctx.log(` ${dstLines.length} lines → ${srcLines.length} lines (${added >= 0 ? '+' : ''}${added})`);
102
-
103
- // 주요 변경 내용 표시 (새로 추가된 라인 중 의미 있는 것)
104
- const dstSet = new Set(dstLines.map(l => l.trim()));
105
- const newLines = srcLines
106
- .filter(l => l.trim() && !l.trim().startsWith('#') && !dstSet.has(l.trim()))
107
- .slice(0, 5);
108
- if (newLines.length > 0) {
109
- ctx.log(' New:');
110
- for (const line of newLines) {
111
- ctx.log(` + ${line.trim().substring(0, 80)}`);
112
- }
113
- }
114
-
115
- if (!dryRun) {
116
- await ctx.writeFile(dst, srcContent);
117
- ctx.success(`Updated: ${dst}`);
118
- }
119
- results.updated.push(dst);
120
- } else {
121
- ctx.log(`${dryRun ? '[DRY] ' : ''}Creating: ${dst}`);
122
- if (!dryRun) {
123
- await ctx.writeFile(dst, srcContent);
124
- ctx.success(`Created: ${dst}`);
125
- }
126
- results.created.push(dst);
127
- }
128
- }
129
-
130
- // 요약
131
- ctx.log('\n=== Update Summary ===');
132
- if (results.updated.length > 0) {
133
- ctx.log(`Updated: ${results.updated.length} file(s)`);
134
- for (const f of results.updated) ctx.log(` ${f}`);
135
- }
136
- if (results.created.length > 0) {
137
- ctx.log(`Created: ${results.created.length} file(s)`);
138
- for (const f of results.created) ctx.log(` ${f}`);
139
- }
140
- if (results.unchanged.length > 0) {
141
- ctx.log(`Unchanged: ${results.unchanged.length} file(s)`);
142
- }
143
- if (results.skipped.length > 0) {
144
- ctx.warn(`Skipped: ${results.skipped.length} file(s)`);
145
- for (const s of results.skipped) ctx.log(` ${s.file} (${s.reason})`);
146
- }
147
-
148
- const totalChanges = results.updated.length + results.created.length;
149
- if (totalChanges === 0) {
150
- ctx.success('\nAlready up to date.');
151
- } else if (dryRun) {
152
- ctx.warn(`\n${totalChanges} file(s) would be changed. Run without --dry to apply.`);
153
- } else {
154
- ctx.success(`\n${totalChanges} file(s) updated to sentix v${VERSION}.`);
155
- ctx.log('Run: sentix doctor — to verify project health');
178
+ await syncFiles(target, dryRun, ctx);
156
179
  }
157
180
  },
158
181
  });