@walwal-harness/cli 6.1.1 → 6.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/CHANGELOG.md +29 -0
- package/README.md +306 -367
- package/assets/templates/config.json +7 -0
- package/assets/templates/progress.json.template +13 -0
- package/gotchas/README.md +40 -8
- package/gotchas/conductor.md +12 -82
- package/gotchas/coo-developer.md +21 -12
- package/gotchas/cqo.md +22 -0
- package/gotchas/cto.md +22 -0
- package/gotchas/dispatcher.md +12 -87
- package/gotchas/documentationer.md +21 -12
- package/gotchas/evaluator-architecture.md +22 -0
- package/gotchas/evaluator-code-quality.md +11 -20
- package/gotchas/evaluator-functional.md +10 -30
- package/gotchas/evaluator-security.md +22 -0
- package/gotchas/evaluator-visual.md +10 -19
- package/gotchas/generator-backend-laravel.md +11 -77
- package/gotchas/generator-backend.md +19 -2
- package/gotchas/generator-designer.md +22 -0
- package/gotchas/generator-devops.md +22 -0
- package/gotchas/generator-frontend.md +13 -22
- package/gotchas/meeting-manager.md +22 -0
- package/gotchas/planner.md +19 -2
- package/gotchas/service-ops.md +13 -18
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +167 -21
- package/scripts/harness-meeting-doc.sh +146 -2
- package/scripts/harness-session-start.sh +61 -0
- package/scripts/harness-token-limit.sh +99 -0
- package/scripts/harness-user-prompt-submit.sh +23 -0
- package/scripts/lib/harness-progress-migrate.sh +58 -0
- package/skills/conductor/SKILL.md +1 -0
- package/skills/coo-developer/SKILL.md +35 -5
- package/skills/documentationer/SKILL.md +55 -9
- package/skills/meeting-manager/SKILL.md +114 -4
- package/skills/planner/SKILL.md +45 -0
|
@@ -1,85 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
docmeta:
|
|
3
3
|
id: gotchas-generator-backend-laravel
|
|
4
|
-
title: Generator-Backend
|
|
5
|
-
type:
|
|
6
|
-
createdAt: 2026-05-
|
|
7
|
-
updatedAt: 2026-05-
|
|
4
|
+
title: Gotchas — Generator-Backend (Laravel/Livewire/Filament)
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-05-08T00:00:00Z
|
|
7
|
+
updatedAt: 2026-05-08T00:00:00Z
|
|
8
8
|
source:
|
|
9
|
-
producer:
|
|
10
|
-
skillId: harness-
|
|
11
|
-
inputs:
|
|
12
|
-
|
|
13
|
-
uri: https://github.com/msitarzewski/agency-agents
|
|
14
|
-
relation: output-from
|
|
15
|
-
sections:
|
|
16
|
-
- sourceRange: { startLine: 1, endLine: 1 }
|
|
17
|
-
targetRange: { startLine: 1, endLine: 999 }
|
|
18
|
-
tags: [gotcha, generator-backend, laravel, livewire, filament, phase-b]
|
|
9
|
+
producer: user
|
|
10
|
+
skillId: harness-dispatcher
|
|
11
|
+
inputs: []
|
|
12
|
+
tags: [gotchas, generator-backend, laravel]
|
|
19
13
|
---
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
# Gotchas — Generator-Backend · Laravel/Livewire/Filament
|
|
22
16
|
|
|
23
|
-
|
|
17
|
+
> 스택별 가드. Generator-Backend 는 Laravel 스택을 감지하면 `gotchas/generator-backend.md` 와 함께 이 파일을 추가로 읽습니다.
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
> 모든 항목은 `unverified` 상태로 시작, Planner 리뷰 후 `verified` 승격.
|
|
27
|
-
|
|
28
|
-
## verified
|
|
29
|
-
|
|
30
|
-
(없음 — 첫 리비전)
|
|
31
|
-
|
|
32
|
-
## unverified
|
|
33
|
-
|
|
34
|
-
### G-LV-001 — Livewire 컴포넌트가 Eloquent 모델을 prop으로 받을 때 직렬화 충돌
|
|
35
|
-
- **언제**: 부모 컴포넌트 → 자식 컴포넌트로 모델을 직접 props 전달
|
|
36
|
-
- **증상**: 새 요청마다 모델 hydrate 실패, lazy load 시 fresh 로드되어 데이터 불일치
|
|
37
|
-
- **회피**: ID만 전달 + 자식이 자기 책임으로 fetch, 또는 `serializeable=true` 명시 + 변경 위험 인지
|
|
38
|
-
|
|
39
|
-
### G-LV-002 — `wire:model.live` 무차별 사용으로 N+1 트래픽 폭발
|
|
40
|
-
- **증상**: 입력 1자마다 서버 왕복, 운영에서 큐 폭주
|
|
41
|
-
- **회피**: 폼 입력 default `wire:model.live.debounce.500ms` 또는 `wire:model.lazy`
|
|
42
|
-
|
|
43
|
-
### G-FL-001 — Filament Resource `eagerLoad()` 누락
|
|
44
|
-
- **증상**: 인덱스 페이지 1회 로드에 컬럼 수 × 행 수 만큼 쿼리 (N+1)
|
|
45
|
-
- **회피**: Resource에 `protected $eagerLoadRelations = ['user', 'category']` 명시. Eval-Architecture가 검출.
|
|
46
|
-
|
|
47
|
-
### G-FL-002 — Filament Tenancy panel에서 Global scope 누락
|
|
48
|
-
- **증상**: 한 테넌트 사용자가 다른 테넌트 데이터 노출
|
|
49
|
-
- **회피**: `getEloquentQuery()` 오버라이드 시 `parent::getEloquentQuery()` 후 scope 추가. Eval-Security 적발.
|
|
50
|
-
|
|
51
|
-
### G-EL-001 — `whereHas` 다단 사용으로 인덱스 미사용 쿼리
|
|
52
|
-
- **증상**: relation 깊이 ≥ 2 + 결과 set 크면 secs 단위
|
|
53
|
-
- **회피**: subquery 또는 `whereIn(select 1 column)` 패턴, EXPLAIN 첨부
|
|
54
|
-
|
|
55
|
-
### G-EL-002 — Mass assignment 누락 → 권한 우회
|
|
56
|
-
- **증상**: 사용자가 `is_admin` 같은 컬럼을 요청 본문에 끼워 권한 상승
|
|
57
|
-
- **회피**: 모든 모델 `$fillable` 또는 `$guarded = []` 의도 명시. Eval-Security CRITICAL.
|
|
58
|
-
|
|
59
|
-
### G-MIG-001 — 인덱스 없는 외래키 컬럼
|
|
60
|
-
- **증상**: parent delete 시 child scan 풀스캔
|
|
61
|
-
- **회피**: 마이그레이션에서 `foreignId(...)->index()` 명시
|
|
62
|
-
|
|
63
|
-
### G-JOB-001 — Job 안에서 retry 비대응 외부 호출
|
|
64
|
-
- **증상**: Stripe·외부 API 호출이 retry 시 중복 결제·중복 메시지
|
|
65
|
-
- **회피**: idempotency_key 또는 `ShouldBeUnique`. Eval-Security 검토.
|
|
66
|
-
|
|
67
|
-
### G-CACHE-001 — `Cache::tags()` on file driver
|
|
68
|
-
- **증상**: 운영에서 silent fail (tag 무시)
|
|
69
|
-
- **회피**: prod cache driver는 redis 강제. config/cache.php boot 시 검증.
|
|
70
|
-
|
|
71
|
-
### G-AUTH-001 — Sanctum 토큰 회전 정책 부재
|
|
72
|
-
- **증상**: 토큰 영구 유효 → 누출 시 영향 무한대
|
|
73
|
-
- **회피**: TTL + 사용 흐름별 ability 분리, 회전 명령 정의 (DevOps와 연계)
|
|
74
|
-
|
|
75
|
-
### G-OBS-001 — observer chain → 부작용 폭발
|
|
76
|
-
- **증상**: User saving → Profile observer → Account observer → 무한 루프 또는 dead-lock
|
|
77
|
-
- **회피**: observer 안에서 다른 모델 save 금지 룰. 명시적 Action 호출로 대체.
|
|
78
|
-
|
|
79
|
-
### G-FORM-001 — `$request->validate()` 인라인 사용
|
|
80
|
-
- **증상**: 검증 룰 재사용·테스트·문서화 모두 어려움
|
|
81
|
-
- **회피**: FormRequest 클래스 강제. Eval-CodeQuality lint 룰.
|
|
82
|
-
|
|
83
|
-
### G-PERF-001 — Telescope prod 활성
|
|
84
|
-
- **증상**: 모든 쿼리·요청 적재 → DB 비대 + 보안 노출
|
|
85
|
-
- **회피**: env 분리, prod에서 `TelescopeServiceProvider` 등록 차단. Eval-Security 적발.
|
|
19
|
+
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
docmeta:
|
|
3
|
+
id: gotchas-generator-backend
|
|
4
|
+
title: Gotchas — ugenerator-ubackend
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-05-08T00:00:00Z
|
|
7
|
+
updatedAt: 2026-05-08T00:00:00Z
|
|
8
|
+
source:
|
|
9
|
+
producer: user
|
|
10
|
+
skillId: harness-dispatcher
|
|
11
|
+
inputs: []
|
|
12
|
+
tags: [gotchas, generator-backend]
|
|
13
|
+
---
|
|
2
14
|
|
|
3
|
-
|
|
15
|
+
# Gotchas — ugenerator-ubackend
|
|
16
|
+
|
|
17
|
+
> Dispatcher 가 관리. ugenerator-ubackend 는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
18
|
+
>
|
|
19
|
+
> 항목 형식은 `gotchas/README.md` 참조. Dispatcher 가 사용자의 실수 지적을 감지하면 자동으로
|
|
20
|
+
> `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
|
|
4
21
|
|
|
5
22
|
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
docmeta:
|
|
3
|
+
id: gotchas-generator-designer
|
|
4
|
+
title: Gotchas — ugenerator-udesigner
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-05-08T00:00:00Z
|
|
7
|
+
updatedAt: 2026-05-08T00:00:00Z
|
|
8
|
+
source:
|
|
9
|
+
producer: user
|
|
10
|
+
skillId: harness-dispatcher
|
|
11
|
+
inputs: []
|
|
12
|
+
tags: [gotchas, generator-designer]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Gotchas — ugenerator-udesigner
|
|
16
|
+
|
|
17
|
+
> Dispatcher 가 관리. ugenerator-udesigner 는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
18
|
+
>
|
|
19
|
+
> 항목 형식은 `gotchas/README.md` 참조. Dispatcher 가 사용자의 실수 지적을 감지하면 자동으로
|
|
20
|
+
> `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
|
|
21
|
+
|
|
22
|
+
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
docmeta:
|
|
3
|
+
id: gotchas-generator-devops
|
|
4
|
+
title: Gotchas — ugenerator-udevops
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-05-08T00:00:00Z
|
|
7
|
+
updatedAt: 2026-05-08T00:00:00Z
|
|
8
|
+
source:
|
|
9
|
+
producer: user
|
|
10
|
+
skillId: harness-dispatcher
|
|
11
|
+
inputs: []
|
|
12
|
+
tags: [gotchas, generator-devops]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Gotchas — ugenerator-udevops
|
|
16
|
+
|
|
17
|
+
> Dispatcher 가 관리. ugenerator-udevops 는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
18
|
+
>
|
|
19
|
+
> 항목 형식은 `gotchas/README.md` 참조. Dispatcher 가 사용자의 실수 지적을 감지하면 자동으로
|
|
20
|
+
> `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
|
|
21
|
+
|
|
22
|
+
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
docmeta:
|
|
3
|
-
id: generator-frontend
|
|
4
|
-
title: Gotchas —
|
|
5
|
-
type:
|
|
6
|
-
createdAt: 2026-05-
|
|
7
|
-
updatedAt: 2026-05-
|
|
3
|
+
id: gotchas-generator-frontend
|
|
4
|
+
title: Gotchas — ugenerator-ufrontend
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-05-08T00:00:00Z
|
|
7
|
+
updatedAt: 2026-05-08T00:00:00Z
|
|
8
8
|
source:
|
|
9
|
-
producer:
|
|
9
|
+
producer: user
|
|
10
10
|
skillId: harness-dispatcher
|
|
11
11
|
inputs: []
|
|
12
|
-
tags: [
|
|
12
|
+
tags: [gotchas, generator-frontend]
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
# Gotchas —
|
|
15
|
+
# Gotchas — ugenerator-ufrontend
|
|
16
16
|
|
|
17
|
-
> Dispatcher가 관리.
|
|
17
|
+
> Dispatcher 가 관리. ugenerator-ufrontend 는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
18
|
+
>
|
|
19
|
+
> 항목 형식은 `gotchas/README.md` 참조. Dispatcher 가 사용자의 실수 지적을 감지하면 자동으로
|
|
20
|
+
> `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **Why**: walwal-harness v6 는 agency-agents (MIT) 의 design/UX/A11y/perf 패턴을 부서별로 흡수했지만, generator-frontend SKILL 에는 sub-skill 호출 의무가 누락되어 CTO-Frontend 가 단독으로 컴포넌트를 찍어내고 끝나는 회귀가 있었다 (moon_web 2026-05-07).
|
|
22
|
-
- **How to apply**:
|
|
23
|
-
- **Sprint Workflow Step 2 (구현)** 진입 시, 다음 sub-skill 결과를 **순차 호출 후 본 구현에 반영**한다 — 결과 미반영은 PASS 금지:
|
|
24
|
-
1. **engineering-react-developer** (또는 -flutter-developer) — 컴포넌트 골격 패턴 결정
|
|
25
|
-
2. **design/ux-strategy** (CTO-Designer 흡수) — 사용자 흐름 / 정보 구조 검증
|
|
26
|
-
3. **design/ui-component-spec** — 토큰 / variant / state 매핑
|
|
27
|
-
4. **engineering-accessibility-reviewer** — WCAG AA + 키보드 네비
|
|
28
|
-
5. **engineering-performance-engineer** — RSC 우선 / hydration 비용 / IntersectionObserver
|
|
29
|
-
- 호출 결과는 sprint-contract.md FE 섹션의 `## Sub-skill Findings` 블록에 요약 반영. 빈 블록 = FAIL.
|
|
30
|
-
- tmux/3D 대시보드에 visibility 보장: 각 sub-skill 호출 직전 `progress.json.meetings.active = ["generator-frontend", "<sub-skill>"]` partial update 후 1.5s, 호출 후 `[]` 로 복귀.
|
|
31
|
-
- **References**: skills/cto/SKILL.md "agency-agents (MIT) 흡수" 섹션, skills/generator-designer/SKILL.md.
|
|
22
|
+
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
docmeta:
|
|
3
|
+
id: gotchas-meeting-manager
|
|
4
|
+
title: Gotchas — umeeting-umanager
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-05-08T00:00:00Z
|
|
7
|
+
updatedAt: 2026-05-08T00:00:00Z
|
|
8
|
+
source:
|
|
9
|
+
producer: user
|
|
10
|
+
skillId: harness-dispatcher
|
|
11
|
+
inputs: []
|
|
12
|
+
tags: [gotchas, meeting-manager]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Gotchas — umeeting-umanager
|
|
16
|
+
|
|
17
|
+
> Dispatcher 가 관리. umeeting-umanager 는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
18
|
+
>
|
|
19
|
+
> 항목 형식은 `gotchas/README.md` 참조. Dispatcher 가 사용자의 실수 지적을 감지하면 자동으로
|
|
20
|
+
> `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
|
|
21
|
+
|
|
22
|
+
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
package/gotchas/planner.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
docmeta:
|
|
3
|
+
id: gotchas-planner
|
|
4
|
+
title: Gotchas — uplanner
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-05-08T00:00:00Z
|
|
7
|
+
updatedAt: 2026-05-08T00:00:00Z
|
|
8
|
+
source:
|
|
9
|
+
producer: user
|
|
10
|
+
skillId: harness-dispatcher
|
|
11
|
+
inputs: []
|
|
12
|
+
tags: [gotchas, planner]
|
|
13
|
+
---
|
|
2
14
|
|
|
3
|
-
|
|
15
|
+
# Gotchas — uplanner
|
|
16
|
+
|
|
17
|
+
> Dispatcher 가 관리. uplanner 는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
18
|
+
>
|
|
19
|
+
> 항목 형식은 `gotchas/README.md` 참조. Dispatcher 가 사용자의 실수 지적을 감지하면 자동으로
|
|
20
|
+
> `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
|
|
4
21
|
|
|
5
22
|
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
package/gotchas/service-ops.md
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
docmeta:
|
|
3
|
-
id: service-ops
|
|
4
|
-
title: Gotchas —
|
|
5
|
-
type:
|
|
6
|
-
createdAt: 2026-05-
|
|
7
|
-
updatedAt: 2026-05-
|
|
3
|
+
id: gotchas-service-ops
|
|
4
|
+
title: Gotchas — uservice-uops
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-05-08T00:00:00Z
|
|
7
|
+
updatedAt: 2026-05-08T00:00:00Z
|
|
8
8
|
source:
|
|
9
|
-
producer:
|
|
9
|
+
producer: user
|
|
10
10
|
skillId: harness-dispatcher
|
|
11
11
|
inputs: []
|
|
12
|
-
tags: [
|
|
12
|
+
tags: [gotchas, service-ops]
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
# Gotchas —
|
|
15
|
+
# Gotchas — uservice-uops
|
|
16
16
|
|
|
17
|
-
> Dispatcher가 관리.
|
|
17
|
+
> Dispatcher 가 관리. uservice-uops 는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
18
|
+
>
|
|
19
|
+
> 항목 형식은 `gotchas/README.md` 참조. Dispatcher 가 사용자의 실수 지적을 감지하면 자동으로
|
|
20
|
+
> `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **Why**: Service-Ops 의 monitor 는 cron + red-alert 이벤트로만 spawn 되도록 설계됐으나, generator/evaluator 가 build·flutter test·deploy 를 실행하는 동안에는 cron 주기 사이 공백이 길어 대시보드에서 "SERVICE-OPS 룸 빈 채" 가 관찰됨 (moon_web 2026-05-07). 빌드 stderr 의 WebSocketChannelException, 컴파일 에러, dart analyze warning 을 실시간으로 캐치하지 못하면 이후 evaluator-functional 단계에서야 발견 → 비용 증가.
|
|
22
|
-
- **How to apply**:
|
|
23
|
-
- Conductor 가 generator-* 또는 evaluator-functional 을 spawn 하기 **직전**, 동일 tick 에 service-ops/monitor 를 **stream-mode 로 함께 spawn** (handoff-bridge). progress.json.service_ops.monitor.stream_active = true.
|
|
24
|
-
- Stream-mode monitor 의 책임: 자식 프로세스 stdout/stderr 를 tail → 정규식 (`error:|exception|TestFailure|Cannot find|Failed to compile`) 매칭 시 즉시 conductor 에 red-alert 발행.
|
|
25
|
-
- 자식 프로세스 종료 시 stream_active = false + ops-report 짧은 요약 append.
|
|
26
|
-
- Visibility: monitor 활성 동안 progress.json.agents 에 service-ops minifig 가 service-ops 룸 (또는 모니터링 대상 룸 인접) 에 위치하도록 partial update.
|
|
27
|
-
- **References**: skills/service-ops/SKILL.md service_ops.monitor 섹션.
|
|
22
|
+
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.3",
|
|
4
4
|
"description": "Production harness for AI agent engineering — NEXUS-adapted company metaphor (Dispatcher/CEO + Conductor + Meeting-Manager + COO/Planner + CTO + CQO + Service-Ops). Solo/Team mode, Brainstormer, Planner, Generator(BE/FE/Designer/DevOps), Evaluator chain (Code-Quality → Functional → Visual + Architecture/Security). Supports React, Next.js, and Flutter FE stacks.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -23,6 +23,10 @@ current_agent=$(jq -r '.current_agent // "null"' "$PROGRESS")
|
|
|
23
23
|
agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS")
|
|
24
24
|
pipeline=$(jq -r '.pipeline // "null"' "$PROGRESS")
|
|
25
25
|
mode=$(jq -r '.mode // "auto"' "$PROGRESS")
|
|
26
|
+
# v6.2 — parallel tracks (fork-join) state
|
|
27
|
+
parallel_tracks=$(jq -c '.conductor.tracks // []' "$PROGRESS")
|
|
28
|
+
parallel_rendezvous=$(jq -c '.conductor.rendezvous // null' "$PROGRESS")
|
|
29
|
+
parallel_pending_count=$(jq -r '[ .conductor.tracks // [] | .[] | select(.status == "pending" or .status == "running") ] | length' "$PROGRESS")
|
|
26
30
|
user_override=$(jq -r '.mode_decision.user_override // "null"' "$PROGRESS")
|
|
27
31
|
requested_mode=$(jq -r '.service_ops.requested_mode // "null"' "$PROGRESS")
|
|
28
32
|
planner_requested_mode=$(jq -r '.planner.requested_mode // "null"' "$PROGRESS")
|
|
@@ -191,6 +195,80 @@ elif [ "$meetings_active_count" -gt 0 ] && [ "$current_agent" != "meeting-manage
|
|
|
191
195
|
next="meeting-manager"
|
|
192
196
|
action="spawn:meeting-manager:${meeting_type}"
|
|
193
197
|
new_conductor_state="waiting_meeting"
|
|
198
|
+
if [ "$meeting_type" = "followup-review" ]; then
|
|
199
|
+
new_workflow_stage="followup-review"
|
|
200
|
+
if [ "$current_agent" = "planner" ] && [ "$planner_requested_mode" = "hypothesis-verdict" ]; then
|
|
201
|
+
planner_filter='.planner.requested_mode = null'
|
|
202
|
+
fi
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# v6.2 — Parallel tracks advance: if a track owner just completed, mark its track done
|
|
206
|
+
# and dispatch the next pending track. When all tracks completed, convene followup-review.
|
|
207
|
+
#
|
|
208
|
+
# Exclusion: for planner running a hypothesis chain (action_type=hypothesis-*), the track is
|
|
209
|
+
# only terminal when the chain ends with planner.last_brief="hypothesis:done". Intermediate
|
|
210
|
+
# planner ticks (research, verdict-prep, etc.) must defer to the dedicated planner-completed
|
|
211
|
+
# branch which routes the chain.
|
|
212
|
+
elif [ "$parallel_pending_count" -gt 0 ] && [ "$agent_status" = "completed" ] && \
|
|
213
|
+
[ "$(jq --arg a "$current_agent" '[.[] | select(.owner == $a and (.status == "running" or .status == "pending"))] | length' <<<"$parallel_tracks")" -gt 0 ] && \
|
|
214
|
+
! { [ "$current_agent" = "planner" ] && \
|
|
215
|
+
[ "$(jq --arg a "$current_agent" '[.[] | select(.owner == $a and (.status == "running" or .status == "pending")) | .action_type // ""] | first | startswith("hypothesis")' <<<"$parallel_tracks")" = "true" ] && \
|
|
216
|
+
[ "$planner_last_brief" != "hypothesis:done" ]; }; then
|
|
217
|
+
# mark the matching running/pending track as completed
|
|
218
|
+
updated_tracks=$(jq -c --arg a "$current_agent" '
|
|
219
|
+
[ .[] |
|
|
220
|
+
if .owner == $a and (.status == "running" or .status == "pending") then
|
|
221
|
+
. + { status: "completed", completed_at: (now | todate) }
|
|
222
|
+
else . end
|
|
223
|
+
]
|
|
224
|
+
' <<<"$parallel_tracks")
|
|
225
|
+
remaining=$(jq -r '[ .[] | select(.status == "pending" or .status == "running") ] | length' <<<"$updated_tracks")
|
|
226
|
+
if [ "$remaining" -gt 0 ]; then
|
|
227
|
+
# dispatch the next pending track
|
|
228
|
+
next_owner=$(jq -r '[ .[] | select(.status == "pending") ] | .[0].owner // empty' <<<"$updated_tracks")
|
|
229
|
+
next_action=$(jq -r '[ .[] | select(.status == "pending") ] | .[0].action_type // "triage"' <<<"$updated_tracks")
|
|
230
|
+
next_track_id=$(jq -r '[ .[] | select(.status == "pending") ] | .[0].id // "track-?"' <<<"$updated_tracks")
|
|
231
|
+
if [ -n "$next_owner" ]; then
|
|
232
|
+
updated_tracks=$(jq -c --arg id "$next_track_id" '
|
|
233
|
+
[ .[] |
|
|
234
|
+
if .id == $id then . + { status: "running", started_at: (now | todate) } else . end
|
|
235
|
+
]
|
|
236
|
+
' <<<"$updated_tracks")
|
|
237
|
+
next="$next_owner"
|
|
238
|
+
action="dispatch:parallel:${next_track_id}:${next_owner}/${next_action}"
|
|
239
|
+
new_workflow_stage="$(meeting_stage_for_owner "$next_owner")"
|
|
240
|
+
if [ "$next_owner" = "planner" ]; then
|
|
241
|
+
planner_filter=".planner.requested_mode = \"$next_action\""
|
|
242
|
+
fi
|
|
243
|
+
meeting_filter=".conductor.tracks = $updated_tracks | .meetings.decision.tracks = $updated_tracks"
|
|
244
|
+
fi
|
|
245
|
+
else
|
|
246
|
+
# All tracks completed → rendezvous (followup-review)
|
|
247
|
+
rdv_type=$(jq -r '.type // "followup-review"' <<<"$parallel_rendezvous")
|
|
248
|
+
fork_meeting_id=$(jq -r '.conductor.fork_meeting_id // ""' "$PROGRESS")
|
|
249
|
+
next="meeting-manager"
|
|
250
|
+
action="convene:${rdv_type}:rendezvous"
|
|
251
|
+
new_conductor_state="waiting_meeting"
|
|
252
|
+
new_workflow_stage="followup-review"
|
|
253
|
+
meeting_prepare=true
|
|
254
|
+
# Save prior_tracks + fork_meeting_id to a stable namespace (.meetings.fork_context)
|
|
255
|
+
# because meeting-doc.sh prepare overwrites .meetings.decision with a new skeleton.
|
|
256
|
+
meeting_filter="
|
|
257
|
+
.meetings.active = [\"meeting-manager\"] |
|
|
258
|
+
.meetings.requested_type = \"$rdv_type\" |
|
|
259
|
+
.meetings.requested_reason = \"rendezvous\" |
|
|
260
|
+
.meetings.requested_tracks = [] |
|
|
261
|
+
.meetings.requested_rendezvous = null |
|
|
262
|
+
.meetings.fork_meeting_id = $(escape_json_string "$fork_meeting_id") |
|
|
263
|
+
.meetings.fork_context = {
|
|
264
|
+
\"fork_meeting_id\": $(escape_json_string "$fork_meeting_id"),
|
|
265
|
+
\"prior_tracks\": $updated_tracks,
|
|
266
|
+
\"sealed_at\": (now | todate)
|
|
267
|
+
} |
|
|
268
|
+
.planner.requested_mode = null |
|
|
269
|
+
.conductor.tracks = $updated_tracks |
|
|
270
|
+
.conductor.rendezvous = null"
|
|
271
|
+
fi
|
|
194
272
|
|
|
195
273
|
elif [ "$requested_mode" != "null" ] && [ -n "$requested_mode" ] && [ "$current_agent" != "service-ops" ]; then
|
|
196
274
|
next="service-ops"
|
|
@@ -224,6 +302,9 @@ elif [ "$current_agent" = "meeting-manager" ] && [ "$agent_status" = "completed"
|
|
|
224
302
|
evidence=$(jq -c '.evidence // []' <<<"$meeting_decision")
|
|
225
303
|
decision_drift=$(jq -r '.drift_classification // "unknown"' <<<"$meeting_decision")
|
|
226
304
|
source_path=$(jq -r '.source_path // "null"' <<<"$meeting_decision")
|
|
305
|
+
# v6.2 — tracks length >= 2 → fork-join, else single. mode 필드 없음.
|
|
306
|
+
decision_tracks=$(jq -c '.tracks // []' <<<"$meeting_decision")
|
|
307
|
+
decision_rendezvous=$(jq -c '.rendezvous // null' <<<"$meeting_decision")
|
|
227
308
|
|
|
228
309
|
if [ "$owner" = "null" ] || [ -z "$owner" ]; then
|
|
229
310
|
case "$decision_drift" in
|
|
@@ -237,30 +318,95 @@ elif [ "$current_agent" = "meeting-manager" ] && [ "$agent_status" = "completed"
|
|
|
237
318
|
esac
|
|
238
319
|
fi
|
|
239
320
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
321
|
+
# v6.2 — Parallel fork: if 2+ tracks, materialize conductor.tracks[] and dispatch
|
|
322
|
+
# the first pending track owner. Subsequent ticks advance remaining tracks.
|
|
323
|
+
if [ "$(jq 'length' <<<"$decision_tracks")" -gt 1 ]; then
|
|
324
|
+
first_track_owner=$(jq -r '.[0].owner' <<<"$decision_tracks")
|
|
325
|
+
first_track_action=$(jq -r '.[0].action_type // "triage"' <<<"$decision_tracks")
|
|
326
|
+
first_track_id=$(jq -r '.[0].id // "track-1"' <<<"$decision_tracks")
|
|
327
|
+
# mark first track as running, rest as pending
|
|
328
|
+
materialized_tracks=$(jq -c --arg first "$first_track_id" '
|
|
329
|
+
[ .[] | . + {
|
|
330
|
+
status: (if .id == $first then "running" else (.status // "pending") end),
|
|
331
|
+
deliverable_path: (.deliverable_path // null)
|
|
332
|
+
}
|
|
333
|
+
]
|
|
334
|
+
' <<<"$decision_tracks")
|
|
335
|
+
next="$first_track_owner"
|
|
336
|
+
action="dispatch:parallel:${first_track_id}:${first_track_owner}/${first_track_action}"
|
|
337
|
+
new_workflow_stage="$(meeting_stage_for_owner "$first_track_owner")"
|
|
338
|
+
if [ "$first_track_owner" = "planner" ]; then
|
|
339
|
+
planner_filter=".planner.requested_mode = \"$first_track_action\""
|
|
340
|
+
fi
|
|
341
|
+
meeting_filter="
|
|
342
|
+
.meetings.active = [] |
|
|
343
|
+
.meetings.last_type = .meetings.requested_type |
|
|
344
|
+
.meetings.last_reason = .meetings.requested_reason |
|
|
345
|
+
.meetings.last_decision = \"fork:${first_track_owner}:${first_track_action}\" |
|
|
346
|
+
.meetings.requested_type = null |
|
|
347
|
+
.meetings.requested_reason = null |
|
|
348
|
+
.meetings.requested_tracks = [] |
|
|
349
|
+
.meetings.requested_rendezvous = null |
|
|
350
|
+
.meetings.decision = {
|
|
351
|
+
\"owner\": \"$owner\",
|
|
352
|
+
\"action_type\": \"$action_type\",
|
|
353
|
+
\"rationale\": $(escape_json_string "$rationale"),
|
|
354
|
+
\"evidence\": $evidence,
|
|
355
|
+
\"drift_classification\": $(escape_json_string "$decision_drift"),
|
|
356
|
+
\"source_path\": $(escape_json_string "$source_path"),
|
|
357
|
+
\"tracks\": $materialized_tracks,
|
|
358
|
+
\"rendezvous\": $decision_rendezvous
|
|
359
|
+
} |
|
|
360
|
+
.conductor.tracks = $materialized_tracks |
|
|
361
|
+
.conductor.rendezvous = $decision_rendezvous |
|
|
362
|
+
.conductor.fork_meeting_id = (.meetings.current_id // null)"
|
|
363
|
+
else
|
|
364
|
+
next="$owner"
|
|
365
|
+
action="dispatch:${owner}:${action_type}"
|
|
366
|
+
new_workflow_stage="$(meeting_stage_for_owner "$owner")"
|
|
367
|
+
if [ "$owner" = "planner" ]; then
|
|
368
|
+
planner_filter=".planner.requested_mode = \"$action_type\""
|
|
369
|
+
fi
|
|
370
|
+
meeting_filter="
|
|
371
|
+
.meetings.active = [] |
|
|
372
|
+
.meetings.last_type = .meetings.requested_type |
|
|
373
|
+
.meetings.last_reason = .meetings.requested_reason |
|
|
374
|
+
.meetings.last_decision = \"$owner:$action_type\" |
|
|
375
|
+
.meetings.requested_type = null |
|
|
376
|
+
.meetings.requested_reason = null |
|
|
377
|
+
.meetings.requested_tracks = [] |
|
|
378
|
+
.meetings.requested_rendezvous = null |
|
|
379
|
+
.meetings.decision = {
|
|
380
|
+
\"owner\": \"$owner\",
|
|
381
|
+
\"action_type\": \"$action_type\",
|
|
382
|
+
\"rationale\": $(escape_json_string "$rationale"),
|
|
383
|
+
\"evidence\": $evidence,
|
|
384
|
+
\"drift_classification\": $(escape_json_string "$decision_drift"),
|
|
385
|
+
\"source_path\": $(escape_json_string "$source_path"),
|
|
386
|
+
\"tracks\": [],
|
|
387
|
+
\"rendezvous\": null
|
|
388
|
+
} |
|
|
389
|
+
.conductor.tracks = [] |
|
|
390
|
+
.conductor.rendezvous = null |
|
|
391
|
+
.conductor.fork_meeting_id = null"
|
|
245
392
|
fi
|
|
246
|
-
meeting_filter="
|
|
247
|
-
.meetings.active = [] |
|
|
248
|
-
.meetings.last_type = .meetings.requested_type |
|
|
249
|
-
.meetings.last_reason = .meetings.requested_reason |
|
|
250
|
-
.meetings.last_decision = \"$owner:$action_type\" |
|
|
251
|
-
.meetings.requested_type = null |
|
|
252
|
-
.meetings.requested_reason = null |
|
|
253
|
-
.meetings.decision = {
|
|
254
|
-
\"owner\": \"$owner\",
|
|
255
|
-
\"action_type\": \"$action_type\",
|
|
256
|
-
\"rationale\": $(escape_json_string "$rationale"),
|
|
257
|
-
\"evidence\": $evidence,
|
|
258
|
-
\"drift_classification\": $(escape_json_string "$decision_drift"),
|
|
259
|
-
\"source_path\": $(escape_json_string "$source_path")
|
|
260
|
-
}"
|
|
261
393
|
|
|
262
394
|
elif [ "$current_agent" = "planner" ] && [ "$agent_status" = "completed" ]; then
|
|
263
|
-
|
|
395
|
+
# v6.2 — hypothesis-verdict is terminal for the cell; return to meeting-manager.
|
|
396
|
+
if [ "$planner_requested_mode" = "hypothesis-verdict" ]; then
|
|
397
|
+
next="meeting-manager"
|
|
398
|
+
action="convene:followup-review:hypothesis-verdict"
|
|
399
|
+
new_workflow_stage="followup-review"
|
|
400
|
+
meeting_prepare=true
|
|
401
|
+
planner_filter='.planner.requested_mode = null'
|
|
402
|
+
meeting_filter="
|
|
403
|
+
.meetings.active = [\"meeting-manager\"] |
|
|
404
|
+
.meetings.requested_type = \"followup-review\" |
|
|
405
|
+
.meetings.requested_reason = \"rendezvous\" |
|
|
406
|
+
.meetings.requested_tracks = [] |
|
|
407
|
+
.meetings.requested_rendezvous = null"
|
|
408
|
+
# v6.2 — Accept hypothesis* (e.g. "hypothesis", "hypothesis-validation") as Hypothesis Cell trigger
|
|
409
|
+
elif [[ "$planner_requested_mode" == hypothesis* ]]; then
|
|
264
410
|
next="documentationer"
|
|
265
411
|
action="dispatch:hypothesis:documentationer"
|
|
266
412
|
new_workflow_stage="coo-hypothesis-research"
|