@walwal-harness/cli 7.1.51 → 7.1.53
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 +14 -1
- package/README.md +3 -3
- package/assets/templates/config.json +2 -0
- package/assets/templates/gitignore-append.txt +3 -1
- package/bin/init.js +44 -10
- package/init.sh +6 -4
- package/package.json +1 -1
- package/scripts/harness-dashboard-up.sh +87 -72
- package/scripts/harness-hourly-review.sh +46 -1
- package/scripts/harness-wake.sh +55 -16
- package/scripts/init-agents-md.sh +2 -1
- package/scripts/init-ref-docs.sh +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,7 @@ docmeta:
|
|
|
4
4
|
title: walwal-harness CHANGELOG
|
|
5
5
|
type: output
|
|
6
6
|
createdAt: 2026-05-07T00:00:00Z
|
|
7
|
-
updatedAt: 2026-06-
|
|
7
|
+
updatedAt: 2026-06-24T00:00:00Z
|
|
8
8
|
source:
|
|
9
9
|
producer: agent
|
|
10
10
|
skillId: harness-release
|
|
@@ -31,6 +31,19 @@ docmeta:
|
|
|
31
31
|
|
|
32
32
|
## Unreleased
|
|
33
33
|
|
|
34
|
+
## 7.1.53 — Write-on-signal documents + `archive/` namespace reclaim (2026-06-24)
|
|
35
|
+
- **`archive/` is now reserved for completed missions, never migrate/init backups.** Every backup snapshot moved out of `.harness/archive/` into a new `.harness/backups/` root: `bin/init.js` (migration snapshot dir, legacy-role migration, pre-harness doc extraction backup, AGENTS/CLAUDE doc backup), `init.sh refresh-ref`, `scripts/init-agents-md.sh`, and `scripts/init-ref-docs.sh`. The Owner expected `archive/` to hold a completed `goal + submission + hot-fix` per goal folder; the harness using it as a migrate backup dump conflicted with that meaning. Files are moved (not deleted), so recovery data is preserved. (Agreement: "archive/ 네임스페이스 탈환".)
|
|
36
|
+
- `.harness/backups/` is added to the managed `.gitignore` block (`assets/templates/gitignore-append.txt`), and **`migrate` now refreshes the host `.gitignore` and best-effort untracks newly-ignored runtime paths** (previously only `init` did this), so migrating projects also stop committing backup/runtime noise.
|
|
37
|
+
- **Hourly review is now write-on-signal.** `scripts/harness-hourly-review.sh` emits a meeting markdown only when the tick carries a real delta — an incident, goal drift, owner escalation, a required-execution contract, a handoff that needs execution, or a change in verdict since the last review. No-delta ticks increment `.meetings.heartbeat_count` instead of writing yet another byte-similar `M-*-hourly` doc (the source of hundreds of boilerplate meeting files). This is safe because the conductor routes off `progress.json` state, not this evidence file, and the no-signal path leaves the last real meeting pointer intact for the `read-decision` fallback. Gated by `company_mode.write_on_signal` (default `true`; `false` restores legacy always-write). (Agreement: 문서 ≠ 텔레메트리 · write-on-signal.)
|
|
38
|
+
- **Wake stops persisting the static prompt.** `scripts/harness-wake.sh` assembles the hourly wake prompt in memory instead of writing a fresh `.harness/actions/wake/wake-<ts>.prompt.md` every tick — those were 100+ byte-identical templates differing only by timestamp/idle/force/review-path. `tmux` mode reuses one overwritten scratch file; each tick appends a single params-only line to `.harness/activity/wake.jsonl` (capped to the last 2000 lines). Per-tick agent logs are written only when an agent actually runs (record mode previously left empty `ops/wake/wake-*.log` files) and are rotated to the most recent 48. (Agreements: 정적 wake.prompt 비영속 · 보존정책 TTL/rotation.)
|
|
39
|
+
- `migrate` injects `company_mode.write_on_signal` into existing `config.json` via a new `configWriteOnSignalMissing` detection, so upgraded projects adopt document suppression automatically.
|
|
40
|
+
|
|
41
|
+
## 7.1.52 — Single shared dashboard install (drop per-project `node_modules`) (2026-06-23)
|
|
42
|
+
- `scripts/harness-dashboard-up.sh` now installs the Brick Office dashboard **once per version** under `~/.walwal-harness/dashboard/<version>/` (override base with `WALWAL_HARNESS_HOME`) and serves every project from that one copy. Previously each project got its own copy plus a full `npm install`, duplicating identical `node_modules` (~597MB) + `.next` (~75MB) per project — 100 projects cost ~64GB of identical bytes. Now total cost is ~640MB regardless of project count.
|
|
43
|
+
- `npm install` + `next build` run only on the first launch of a given version (guarded by a `.walwal-dashboard-ready` marker and an atomic `mkdir` build lock so concurrent launches don't race). Each project is then served with its own `HARNESS_ROOT` + `--port` via `next start`; since `next start` reads `.next` read-only, many dashboards run concurrently from the same install safely.
|
|
44
|
+
- This is correct because the dashboard code is byte-identical across projects and `HARNESS_ROOT` is resolved at request time (`lib/harness-root.ts`; all routes are `force-dynamic`), so build output never bakes in a project. Supersedes both the pre-7.1.37 per-project-hashed home cache and the 7.1.37 project-local `.harness/dashboard/` copy.
|
|
45
|
+
- `bin/init.js` now writes hook/statusLine commands in `settings.json` as project-root-relative paths (`bash scripts/...`) instead of absolute `PROJECT_ROOT` paths, so moving or renaming a project no longer breaks the harness Stop hook with `No such file` errors.
|
|
46
|
+
|
|
34
47
|
## 7.1.48 — Auto-managed `.harness` `.gitignore` + runtime untrack on init (2026-06-05)
|
|
35
48
|
- `walwal-harness init` now writes/refreshes a marked managed block in the project-root `.gitignore` so machine-specific harness runtime/local state is never committed and never pollutes teammates' diffs. Ignored: live conductor `progress.json`/`progress.log`/`handoff.json`/`events.jsonl`, the `todos/` live queue (`state.json` + `events.jsonl`), `actions/`, `ops/`, `activity/`, `logs/`, `archive/` migration backups, and the `dashboard/` visualization tooling. Durable team knowledge stays tracked: `conventions/`, `gotchas/`, `documents/`, `memories/`, `memory.md`, `shared/`, `config.json`, `HARNESS.md`, `.bundle-version`.
|
|
36
49
|
- Init also best-effort `git rm --cached`s any of those runtime paths committed before the ignore block existed (files stay on disk), fixing the "`.gitignore` added but already-tracked files keep showing as changes" case.
|
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ Run on a different port:
|
|
|
164
164
|
bash scripts/harness-dashboard-up.sh --port 3002
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
Force-
|
|
167
|
+
Force-rebuild the shared dashboard install when an older dashboard is still shown:
|
|
168
168
|
|
|
169
169
|
```bash
|
|
170
170
|
bash scripts/harness-dashboard-up.sh --port 3002 --reinstall
|
|
@@ -175,9 +175,9 @@ Dashboard options:
|
|
|
175
175
|
| Option | Role |
|
|
176
176
|
|---|---|
|
|
177
177
|
| `--port <port>` | Run Brick Office on a custom port, e.g. `3002` |
|
|
178
|
-
| `--reinstall` |
|
|
178
|
+
| `--reinstall` | Discard and rebuild the shared dashboard install for this version |
|
|
179
179
|
|
|
180
|
-
The dashboard
|
|
180
|
+
The dashboard code is identical across all projects, so it is installed **once per version** under `~/.walwal-harness/dashboard/<version>/` and shared by every project — a single `node_modules` plus one production build (`npm install` + `next build`) the first time, then reused. Each project is served with its own `HARNESS_ROOT` and port via `next start`, so you can run many dashboards at once without duplicating dependencies (100 projects still cost ~640MB, not 100×). Override the base directory with `WALWAL_HARNESS_HOME`.
|
|
181
181
|
|
|
182
182
|
### Company Auto-Chain State
|
|
183
183
|
|
|
@@ -190,6 +190,8 @@
|
|
|
190
190
|
"hourly_wake_model_description": "Claude executor 에서 특정 모델을 강제하려면 HARNESS_WAKE_MODEL 또는 이 값을 사용한다. 비워두면 CLI 기본 모델을 사용한다.",
|
|
191
191
|
"hourly_wake_mode": "headless",
|
|
192
192
|
"hourly_wake_mode_description": "headless 는 백그라운드 실행, tmux 는 매시간 별도 tmux 세션, record 는 prompt/log만 생성한다.",
|
|
193
|
+
"write_on_signal": true,
|
|
194
|
+
"write_on_signal_description": "true 이면 hourly-review 가 델타(인시던트/목표드리프트/실행계약/판정 변화)가 있는 틱에서만 meeting 문서를 생성하고, 무신호 틱은 .meetings.heartbeat_count 만 증가시킨다. 문서≠텔레메트리·write-on-signal 합의에 따라 동일 boilerplate 문서 폭증을 막는다. false 로 두면 매 틱 문서를 남기는 레거시 동작.",
|
|
193
195
|
"autonomous": true,
|
|
194
196
|
"parallel_by_default": true
|
|
195
197
|
},
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
.harness/ops/
|
|
20
20
|
.harness/activity/
|
|
21
21
|
.harness/logs/
|
|
22
|
-
# Migration
|
|
22
|
+
# Migration / init / refresh backup snapshots (local-only — never shared)
|
|
23
|
+
.harness/backups/
|
|
24
|
+
# Completed-mission archive + sprint outputs (local runtime; goal+submission+hot-fix)
|
|
23
25
|
.harness/archive/
|
|
24
26
|
# Brick Office dashboard — local visualization tool + per-env data & build artifacts
|
|
25
27
|
.harness/dashboard/
|
package/bin/init.js
CHANGED
|
@@ -190,7 +190,11 @@ function shellQuote(value) {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
function harnessScriptCommand(scriptName) {
|
|
193
|
-
|
|
193
|
+
// Relative path (resolved from the project root — where Claude Code runs hooks
|
|
194
|
+
// and the statusLine). A relative path survives the project being moved or
|
|
195
|
+
// renamed; an absolute PROJECT_ROOT path bakes in the install-time location
|
|
196
|
+
// and breaks the moment the directory is relocated.
|
|
197
|
+
return `bash ${shellQuote(path.join('scripts', scriptName))}`;
|
|
194
198
|
}
|
|
195
199
|
|
|
196
200
|
function replaceHarnessHookCommand(entries, marker, command) {
|
|
@@ -282,7 +286,9 @@ function extractMarkdownEntryBlocks(md, prefix) {
|
|
|
282
286
|
function migrateLegacyRoleEntries(dir, files, kind) {
|
|
283
287
|
const prefix = kind === 'gotchas' ? 'G' : 'C';
|
|
284
288
|
const migrated = [];
|
|
285
|
-
|
|
289
|
+
// Backup snapshots live under .harness/backups/, never .harness/archive/ —
|
|
290
|
+
// archive/ is reserved for completed-mission outputs (goal+submission+hot-fix).
|
|
291
|
+
const backupDir = path.join(HARNESS_DIR, 'backups', 'pre-v7-role-migration');
|
|
286
292
|
for (const file of files) {
|
|
287
293
|
const sourcePath = path.join(dir, file);
|
|
288
294
|
if (!fs.existsSync(sourcePath)) continue;
|
|
@@ -382,8 +388,9 @@ function migrateExistingDocs() {
|
|
|
382
388
|
continue;
|
|
383
389
|
}
|
|
384
390
|
|
|
385
|
-
// Backup
|
|
386
|
-
const backupPath = path.join(HARNESS_DIR, '
|
|
391
|
+
// Backup (under .harness/backups/, not archive/ — archive holds completed missions)
|
|
392
|
+
const backupPath = path.join(HARNESS_DIR, 'backups', `pre-harness-${path.basename(docPath)}.bak`);
|
|
393
|
+
ensureDir(path.dirname(backupPath));
|
|
387
394
|
fs.writeFileSync(backupPath, content);
|
|
388
395
|
report.push(`Backed up: ${docPath} → ${backupPath}`);
|
|
389
396
|
|
|
@@ -460,7 +467,7 @@ function migrateExistingDocs() {
|
|
|
460
467
|
``,
|
|
461
468
|
`## Backups`,
|
|
462
469
|
``,
|
|
463
|
-
`Original documents were preserved in \`.harness/
|
|
470
|
+
`Original documents were preserved in \`.harness/backups/pre-harness-*.md.bak\`.`,
|
|
464
471
|
``
|
|
465
472
|
].join('\n');
|
|
466
473
|
fs.writeFileSync(reportPath, reportContent);
|
|
@@ -1421,7 +1428,7 @@ function normalizeDocBody(body) {
|
|
|
1421
1428
|
|
|
1422
1429
|
function ensureProjectAgentDocs(opts = {}) {
|
|
1423
1430
|
const dryRun = opts.dryRun || false;
|
|
1424
|
-
const backupDir = opts.backupDir || path.join(HARNESS_DIR, '
|
|
1431
|
+
const backupDir = opts.backupDir || path.join(HARNESS_DIR, 'backups', 'pre-harness-backup');
|
|
1425
1432
|
const agentsMd = path.join(PROJECT_ROOT, 'AGENTS.md');
|
|
1426
1433
|
const claudeMd = path.join(PROJECT_ROOT, 'CLAUDE.md');
|
|
1427
1434
|
|
|
@@ -1481,7 +1488,7 @@ function ensureProjectAgentDocs(opts = {}) {
|
|
|
1481
1488
|
function setupAgentsMd() {
|
|
1482
1489
|
const agentsMd = path.join(PROJECT_ROOT, 'AGENTS.md');
|
|
1483
1490
|
const claudeMd = path.join(PROJECT_ROOT, 'CLAUDE.md');
|
|
1484
|
-
ensureProjectAgentDocs({ backupDir: path.join(HARNESS_DIR, '
|
|
1491
|
+
ensureProjectAgentDocs({ backupDir: path.join(HARNESS_DIR, 'backups', 'pre-harness-backup') });
|
|
1485
1492
|
}
|
|
1486
1493
|
|
|
1487
1494
|
// ─────────────────────────────────────────
|
|
@@ -1638,6 +1645,7 @@ function detectMigrationNeeded() {
|
|
|
1638
1645
|
configLegacyRouting: false,
|
|
1639
1646
|
configRuntimeVerificationMissing: false,
|
|
1640
1647
|
configWakeModelMissing: false,
|
|
1648
|
+
configWriteOnSignalMissing: false,
|
|
1641
1649
|
coreSkillsStale: false,
|
|
1642
1650
|
hrResourcePoolStale: false,
|
|
1643
1651
|
harnessMdStale: false,
|
|
@@ -1747,6 +1755,9 @@ function detectMigrationNeeded() {
|
|
|
1747
1755
|
if (c.company_mode && c.company_mode.hourly_wake_model === undefined) {
|
|
1748
1756
|
flags.configWakeModelMissing = true;
|
|
1749
1757
|
}
|
|
1758
|
+
if (c.company_mode && c.company_mode.write_on_signal === undefined) {
|
|
1759
|
+
flags.configWriteOnSignalMissing = true;
|
|
1760
|
+
}
|
|
1750
1761
|
if (
|
|
1751
1762
|
c.behavior?.auto_route_dispatcher !== undefined ||
|
|
1752
1763
|
c.behavior?.auto_route_dispatcher_description ||
|
|
@@ -2031,6 +2042,10 @@ function showMigrationProposal(flags) {
|
|
|
2031
2042
|
console.log(' • config.json: company_mode.hourly_wake_model 추가 가능');
|
|
2032
2043
|
console.log(' Claude wake tick 에서 --model 을 환경변수/설정으로 지정할 수 있습니다.');
|
|
2033
2044
|
}
|
|
2045
|
+
if (flags.configWriteOnSignalMissing) {
|
|
2046
|
+
console.log(' • config.json: company_mode.write_on_signal 추가 가능');
|
|
2047
|
+
console.log(' 무신호(델타 부재) 틱은 meeting 문서 대신 heartbeat 만 남겨 문서 폭증을 막습니다.');
|
|
2048
|
+
}
|
|
2034
2049
|
if (flags.configLegacyRouting) {
|
|
2035
2050
|
console.log(' • config.json: legacy dispatcher/conductor wording → v7 CEO/CXX wording');
|
|
2036
2051
|
}
|
|
@@ -2087,7 +2102,7 @@ function showMigrationProposal(flags) {
|
|
|
2087
2102
|
console.log(' 미리보기: npx walwal-harness migrate --dry-run');
|
|
2088
2103
|
console.log('');
|
|
2089
2104
|
console.log(' ※ 자동 강제 X — 사용자가 명령을 실행할 때만 변경됩니다.');
|
|
2090
|
-
console.log(' ※ 변경 전 .harness/
|
|
2105
|
+
console.log(' ※ 변경 전 .harness/backups/migration-<ts>/ 에 자동 백업 (archive/ 는 완료 미션 전용).');
|
|
2091
2106
|
console.log('');
|
|
2092
2107
|
}
|
|
2093
2108
|
|
|
@@ -2114,6 +2129,7 @@ function runMigrate(opts = {}) {
|
|
|
2114
2129
|
!flags.configLegacyRouting &&
|
|
2115
2130
|
!flags.configRuntimeVerificationMissing &&
|
|
2116
2131
|
!flags.configWakeModelMissing &&
|
|
2132
|
+
!flags.configWriteOnSignalMissing &&
|
|
2117
2133
|
!flags.coreSkillsStale &&
|
|
2118
2134
|
!flags.hrResourcePoolStale &&
|
|
2119
2135
|
!flags.harnessMdStale &&
|
|
@@ -2140,9 +2156,20 @@ function runMigrate(opts = {}) {
|
|
|
2140
2156
|
log(dryRun ? '=== DRY RUN — 실제 변경 없음 ===' : '=== Migration 적용 ===');
|
|
2141
2157
|
|
|
2142
2158
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
2143
|
-
|
|
2159
|
+
// Migration backups go to .harness/backups/, not .harness/archive/. archive/ is
|
|
2160
|
+
// the completed-mission namespace (goal+submission+hot-fix); it must not collect
|
|
2161
|
+
// migrate snapshots. (Owner agreement: "archive/ 네임스페이스 탈환".)
|
|
2162
|
+
const backupDir = path.join(HARNESS_DIR, 'backups', `migration-${ts}`);
|
|
2144
2163
|
if (!dryRun) ensureDir(backupDir);
|
|
2145
2164
|
ensureStructuredRuntimeFiles({ dryRun });
|
|
2165
|
+
// Keep the host .gitignore in sync so migrating projects also ignore the new
|
|
2166
|
+
// .harness/backups/ snapshot root (and drop any previously-tracked runtime noise).
|
|
2167
|
+
if (!dryRun) {
|
|
2168
|
+
ensureProjectGitignore();
|
|
2169
|
+
gitUntrackLocalState();
|
|
2170
|
+
} else {
|
|
2171
|
+
log(' (dry-run) .gitignore: walwal-harness managed block refresh 예정 (.harness/backups/ 포함)');
|
|
2172
|
+
}
|
|
2146
2173
|
ensureProjectAgentDocs({ dryRun, backupDir });
|
|
2147
2174
|
flags.agentsMissingOpsVerificationRules = false;
|
|
2148
2175
|
flags.agentsMissingCodexAdapterRules = false;
|
|
@@ -2190,7 +2217,7 @@ function runMigrate(opts = {}) {
|
|
|
2190
2217
|
// 2. config.json — inject company_mode/runtime verification from template if missing
|
|
2191
2218
|
const configPath = path.join(HARNESS_DIR, 'config.json');
|
|
2192
2219
|
const tplPath = path.join(PKG_ROOT, 'assets', 'templates', 'config.json');
|
|
2193
|
-
if ((flags.configMissingCompanyMode || flags.configLegacyRouting || flags.configRuntimeVerificationMissing || flags.configWakeModelMissing) && fs.existsSync(configPath) && fs.existsSync(tplPath)) {
|
|
2220
|
+
if ((flags.configMissingCompanyMode || flags.configLegacyRouting || flags.configRuntimeVerificationMissing || flags.configWakeModelMissing || flags.configWriteOnSignalMissing) && fs.existsSync(configPath) && fs.existsSync(tplPath)) {
|
|
2194
2221
|
const original = fs.readFileSync(configPath, 'utf8');
|
|
2195
2222
|
const c = JSON.parse(original);
|
|
2196
2223
|
const tpl = JSON.parse(fs.readFileSync(tplPath, 'utf8'));
|
|
@@ -2221,6 +2248,13 @@ function runMigrate(opts = {}) {
|
|
|
2221
2248
|
configChanged = true;
|
|
2222
2249
|
log(' config.json: hourly_wake_model 섹션 동기화');
|
|
2223
2250
|
}
|
|
2251
|
+
if (flags.configWriteOnSignalMissing) {
|
|
2252
|
+
c.company_mode = c.company_mode || {};
|
|
2253
|
+
c.company_mode.write_on_signal = tpl.company_mode.write_on_signal ?? true;
|
|
2254
|
+
c.company_mode.write_on_signal_description = tpl.company_mode.write_on_signal_description;
|
|
2255
|
+
configChanged = true;
|
|
2256
|
+
log(' config.json: write_on_signal (무신호 틱 문서 억제) 섹션 동기화');
|
|
2257
|
+
}
|
|
2224
2258
|
if (flags.configRuntimeVerificationMissing && tpl.runtime?.verification) {
|
|
2225
2259
|
c.runtime = c.runtime || {};
|
|
2226
2260
|
c.runtime.verification = deepMerge(tpl.runtime.verification, c.runtime.verification || {});
|
package/init.sh
CHANGED
|
@@ -181,13 +181,15 @@ run_refresh_ref() {
|
|
|
181
181
|
fi
|
|
182
182
|
|
|
183
183
|
local ref_path="${project_root}/.harness/ref/${role}-${stack}.md"
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
# Ref backups go to .harness/backups/, not .harness/archive/. archive/ is the
|
|
185
|
+
# completed-mission namespace and must not collect tooling backups.
|
|
186
|
+
local backups_dir="${project_root}/.harness/backups/refs"
|
|
187
|
+
mkdir -p "$backups_dir"
|
|
186
188
|
|
|
187
189
|
if [ -f "$ref_path" ]; then
|
|
188
|
-
local backup="${
|
|
190
|
+
local backup="${backups_dir}/ref-${role}-${stack}-$(date +%Y%m%d%H%M%S).md"
|
|
189
191
|
mv "$ref_path" "$backup"
|
|
190
|
-
echo "
|
|
192
|
+
echo "backup: $backup"
|
|
191
193
|
fi
|
|
192
194
|
|
|
193
195
|
echo ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.53",
|
|
4
4
|
"description": "Company-style AI agent harness for Claude and Codex. Installs commands, CXX agents, skills, HR-Resource hiring pool, and project-local .harness runtime state.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# walwal-harness — Brick Office dashboard launcher
|
|
2
|
+
# walwal-harness — Brick Office dashboard launcher (shared single-install)
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
4
|
+
# 대시보드 코드는 모든 프로젝트에서 byte 단위로 동일하다 (패키지가 버전 고정으로
|
|
5
|
+
# 배포). 프로젝트마다 다른 것은 HARNESS_ROOT(어느 .harness/ 를 볼지)와 PORT 뿐이고,
|
|
6
|
+
# HARNESS_ROOT 는 lib/harness-root.ts 가 *런타임*에 읽는다. 따라서 설치본은 하나면
|
|
7
|
+
# 충분하다.
|
|
8
|
+
#
|
|
9
|
+
# 이 런처는 버전별 단일 설치본을 ~/.walwal-harness/dashboard/<version>/ 에 두고
|
|
10
|
+
# (npm install + next build 를 버전당 딱 한 번), 각 프로젝트는 HARNESS_ROOT+PORT 만
|
|
11
|
+
# 바꿔 `next start` 로 띄운다. next start 는 .next 를 읽기전용으로 쓰므로 같은 폴더에서
|
|
12
|
+
# 여러 프로젝트를 동시에 서빙해도 안전하다. 프로젝트가 100개여도 node_modules/.next 는
|
|
13
|
+
# 단 1벌(~640MB).
|
|
14
|
+
#
|
|
15
|
+
# 구버전 호환: 패키지에 dashboard 가 없으면 git sparse-checkout 으로 fallback 한다.
|
|
8
16
|
#
|
|
9
17
|
# Usage:
|
|
10
|
-
# bash scripts/harness-dashboard-up.sh #
|
|
18
|
+
# bash scripts/harness-dashboard-up.sh # build(최초 1회) + serve (port 3001)
|
|
11
19
|
# bash scripts/harness-dashboard-up.sh --port 3050 # override port
|
|
20
|
+
# bash scripts/harness-dashboard-up.sh --reinstall # 공유 설치본 폐기 후 재빌드
|
|
12
21
|
# HARNESS_ROOT=/path/to/project bash ... # explicit harness root
|
|
13
22
|
set -euo pipefail
|
|
14
23
|
|
|
@@ -23,12 +32,13 @@ while [[ $# -gt 0 ]]; do
|
|
|
23
32
|
--port) PORT="$2"; shift 2 ;;
|
|
24
33
|
--reinstall) REINSTALL=true; shift ;;
|
|
25
34
|
--help|-h)
|
|
26
|
-
sed -n '2,
|
|
35
|
+
sed -n '2,28p' "$0"
|
|
27
36
|
exit 0 ;;
|
|
28
37
|
*) echo "[brick-office] unknown arg: $1" >&2; exit 2 ;;
|
|
29
38
|
esac
|
|
30
39
|
done
|
|
31
40
|
|
|
41
|
+
# --- target project (the one whose .harness/ we visualize) ---
|
|
32
42
|
if [[ -z "${HARNESS_ROOT:-}" ]]; then
|
|
33
43
|
HARNESS_ROOT="$(pwd)"
|
|
34
44
|
fi
|
|
@@ -38,87 +48,91 @@ if [[ ! -d "${HARNESS_ROOT}/.harness" ]]; then
|
|
|
38
48
|
exit 3
|
|
39
49
|
fi
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
VERSION_FILE="${LOCAL_DIR}/.walwal-dashboard-version"
|
|
43
|
-
|
|
51
|
+
# --- locate the packaged dashboard source + its version ---
|
|
44
52
|
PACKAGE_ROOT="${HARNESS_ROOT}/node_modules/@walwal-harness/cli"
|
|
45
53
|
PACKAGED_DASHBOARD="${PACKAGE_ROOT}/${DASHBOARD_PATH}"
|
|
46
54
|
if [[ -f "${PACKAGE_ROOT}/package.json" ]]; then
|
|
47
55
|
PACKAGE_VERSION="$(node -p "require('${PACKAGE_ROOT}/package.json').version" 2>/dev/null || echo unknown)"
|
|
48
56
|
fi
|
|
49
57
|
|
|
58
|
+
# --- shared, version-keyed install (the whole point: one copy for ALL projects) ---
|
|
59
|
+
SHARED_BASE="${WALWAL_HARNESS_HOME:-$HOME/.walwal-harness}/dashboard"
|
|
60
|
+
VERSION_KEY="${PACKAGE_VERSION}"
|
|
61
|
+
[[ "$VERSION_KEY" == "unknown" || -z "$VERSION_KEY" ]] && VERSION_KEY="edge"
|
|
62
|
+
SHARED_DIR="${SHARED_BASE}/${VERSION_KEY}" # node_modules + .next + source live here
|
|
63
|
+
READY_MARKER="${SHARED_DIR}/.walwal-dashboard-ready" # written only after a successful build
|
|
64
|
+
LOCK_DIR="${SHARED_BASE}/.build-lock-${VERSION_KEY}" # mkdir-based build mutex (atomic)
|
|
65
|
+
|
|
50
66
|
if [[ "$REINSTALL" == "true" ]]; then
|
|
51
|
-
rm -rf "$
|
|
67
|
+
rm -rf "$SHARED_DIR"
|
|
52
68
|
fi
|
|
53
69
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
local tpl="${PACKAGE_ROOT}/assets/templates/gitignore-append.txt"
|
|
65
|
-
local block
|
|
66
|
-
if [[ -f "$tpl" ]]; then
|
|
67
|
-
block="$(cat "$tpl")"
|
|
70
|
+
echo "[brick-office] HARNESS_ROOT = ${HARNESS_ROOT}"
|
|
71
|
+
echo "[brick-office] shared install = ${SHARED_DIR} (version ${VERSION_KEY})"
|
|
72
|
+
|
|
73
|
+
# Copy the dashboard source into the shared dir. Leaves any existing node_modules
|
|
74
|
+
# in place (the package tarball ships no node_modules), so a re-entry after a failed
|
|
75
|
+
# build keeps the installed deps.
|
|
76
|
+
populate_source() {
|
|
77
|
+
mkdir -p "$SHARED_DIR"
|
|
78
|
+
if [[ -d "${PACKAGED_DASHBOARD}" ]]; then
|
|
79
|
+
cp -R "${PACKAGED_DASHBOARD}/." "${SHARED_DIR}/"
|
|
68
80
|
else
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
echo "[brick-office] 패키지에 dashboard 없음 — git sparse-checkout fallback (~5MB)."
|
|
82
|
+
local dl="${SHARED_BASE}/.download-${VERSION_KEY}"
|
|
83
|
+
rm -rf "$dl"; mkdir -p "$dl"
|
|
84
|
+
(
|
|
85
|
+
cd "$dl"
|
|
86
|
+
git init -q
|
|
87
|
+
git remote add origin "${REPO_URL}" 2>/dev/null || true
|
|
88
|
+
git config core.sparseCheckout true
|
|
89
|
+
echo "${DASHBOARD_PATH}/" > .git/info/sparse-checkout
|
|
90
|
+
git fetch --depth=1 origin main -q
|
|
91
|
+
git checkout main -q
|
|
92
|
+
)
|
|
93
|
+
cp -R "${dl}/${DASHBOARD_PATH}/." "${SHARED_DIR}/"
|
|
94
|
+
rm -rf "$dl"
|
|
78
95
|
fi
|
|
79
|
-
printf '%s\n' "$block" >> "$gitignore"
|
|
80
96
|
}
|
|
81
97
|
|
|
82
|
-
|
|
98
|
+
# build = install deps + production build, guarded by a single-writer lock so two
|
|
99
|
+
# projects launching at once don't race on the same shared dir. The EXIT trap is a
|
|
100
|
+
# safety net: under `set -e` a failed npm install / next build aborts the script, and
|
|
101
|
+
# the trap then frees the lock (and leaves no READY_MARKER → next launch rebuilds).
|
|
102
|
+
build_shared() {
|
|
103
|
+
local waited=0
|
|
104
|
+
mkdir -p "$SHARED_BASE" # lock dir is created with plain mkdir, so its parent must exist
|
|
105
|
+
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
106
|
+
# someone else finished the build while we were waiting → done, nothing to own.
|
|
107
|
+
[[ -f "$READY_MARKER" ]] && return 0
|
|
108
|
+
sleep 1; waited=$((waited + 1))
|
|
109
|
+
if (( waited > 600 )); then
|
|
110
|
+
echo "[brick-office] stale build lock (>10m) — 강제 해제." >&2
|
|
111
|
+
rm -rf "$LOCK_DIR"
|
|
112
|
+
fi
|
|
113
|
+
done
|
|
114
|
+
trap 'rm -rf "$LOCK_DIR"' EXIT
|
|
83
115
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if [[ -d "${PACKAGED_DASHBOARD}" ]]; then
|
|
88
|
-
INSTALLED_VERSION=""
|
|
89
|
-
if [[ -f "${VERSION_FILE}" ]]; then
|
|
90
|
-
INSTALLED_VERSION="$(cat "${VERSION_FILE}" 2>/dev/null || true)"
|
|
91
|
-
fi
|
|
92
|
-
if [[ ! -d "${LOCAL_DIR}/${DASHBOARD_PATH}" || "${INSTALLED_VERSION}" != "${PACKAGE_VERSION}" ]]; then
|
|
93
|
-
echo "[brick-office] dashboard sync from npm package @walwal-harness/cli@${PACKAGE_VERSION}"
|
|
94
|
-
rm -rf "${LOCAL_DIR:?}/${DASHBOARD_PATH}"
|
|
95
|
-
mkdir -p "${LOCAL_DIR}/apps"
|
|
96
|
-
cp -R "${PACKAGED_DASHBOARD}" "${LOCAL_DIR}/apps/"
|
|
97
|
-
printf '%s\n' "${PACKAGE_VERSION}" > "${VERSION_FILE}"
|
|
116
|
+
# double-checked: another waiter may have built it just before we got the lock.
|
|
117
|
+
if [[ -f "$READY_MARKER" && "$(cat "$READY_MARKER" 2>/dev/null || true)" == "$VERSION_KEY" ]]; then
|
|
118
|
+
rm -rf "$LOCK_DIR"; trap - EXIT; return 0
|
|
98
119
|
fi
|
|
99
|
-
elif [[ ! -d "${LOCAL_DIR}/${DASHBOARD_PATH}" ]]; then
|
|
100
|
-
echo "[brick-office] 첫 실행 — git sparse-checkout 으로 dashboard 만 가져옵니다 (~5MB)."
|
|
101
|
-
rm -rf "${LOCAL_DIR}/.download"
|
|
102
|
-
mkdir -p "${LOCAL_DIR}/.download"
|
|
103
|
-
cd "${LOCAL_DIR}/.download"
|
|
104
|
-
git init -q
|
|
105
|
-
git remote add origin "${REPO_URL}" 2>/dev/null || true
|
|
106
|
-
git config core.sparseCheckout true
|
|
107
|
-
echo "${DASHBOARD_PATH}/" > .git/info/sparse-checkout
|
|
108
|
-
git fetch --depth=1 origin main -q
|
|
109
|
-
git checkout main -q
|
|
110
|
-
mkdir -p "${LOCAL_DIR}/apps"
|
|
111
|
-
rm -rf "${LOCAL_DIR:?}/${DASHBOARD_PATH}"
|
|
112
|
-
cp -R "${DASHBOARD_PATH}" "${LOCAL_DIR}/apps/"
|
|
113
|
-
rm -rf "${LOCAL_DIR}/.download"
|
|
114
|
-
echo "[brick-office] 다운로드 완료."
|
|
115
|
-
fi
|
|
116
120
|
|
|
117
|
-
|
|
121
|
+
[[ -f "${SHARED_DIR}/package.json" ]] || populate_source
|
|
122
|
+
|
|
123
|
+
echo "[brick-office] npm install (버전당 1회, ~30s)..."
|
|
124
|
+
( cd "$SHARED_DIR" && npm install --no-audit --no-fund --loglevel=error )
|
|
125
|
+
|
|
126
|
+
echo "[brick-office] next build (버전당 1회)..."
|
|
127
|
+
( cd "$SHARED_DIR" && ./node_modules/.bin/next build )
|
|
128
|
+
|
|
129
|
+
printf '%s\n' "$VERSION_KEY" > "$READY_MARKER"
|
|
130
|
+
echo "[brick-office] 빌드 완료 — 이후 모든 프로젝트가 이 설치본을 공유합니다."
|
|
131
|
+
rm -rf "$LOCK_DIR"; trap - EXIT
|
|
132
|
+
}
|
|
118
133
|
|
|
119
|
-
if [[ ! -
|
|
120
|
-
|
|
121
|
-
npm install --no-audit --no-fund --loglevel=error
|
|
134
|
+
if [[ ! -f "$READY_MARKER" || "$(cat "$READY_MARKER" 2>/dev/null || true)" != "$VERSION_KEY" ]]; then
|
|
135
|
+
build_shared
|
|
122
136
|
fi
|
|
123
137
|
|
|
124
138
|
echo ""
|
|
@@ -130,4 +144,5 @@ echo "║ 실시간 (SSE) 시각화합니다. Ctrl+C 로 종료.
|
|
|
130
144
|
echo "╚══════════════════════════════════════════════════════════╝"
|
|
131
145
|
echo ""
|
|
132
146
|
|
|
133
|
-
|
|
147
|
+
cd "$SHARED_DIR"
|
|
148
|
+
HARNESS_ROOT="${HARNESS_ROOT}" exec ./node_modules/.bin/next start -H 127.0.0.1 -p "${PORT}"
|
|
@@ -40,7 +40,9 @@ meeting_rel=".harness/actions/meetings/$id/meeting-$id.md"
|
|
|
40
40
|
meeting_path="$PROJECT_ROOT/$meeting_rel"
|
|
41
41
|
log_path="$PROJECT_ROOT/.harness/progress.log"
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
# meeting_dir is created lazily, only when this tick actually has a signal worth a
|
|
44
|
+
# meeting doc (see the write-on-signal gate below). No-signal ticks must not even
|
|
45
|
+
# leave an empty M-*-hourly directory behind.
|
|
44
46
|
|
|
45
47
|
ops_report=""
|
|
46
48
|
if [ -x "$SCRIPT_DIR/harness-service-ops-monitor.sh" ]; then
|
|
@@ -300,6 +302,49 @@ if [ "$verdict" = "working" ] && [ "$handoff_requires_execution" = "true" ]; the
|
|
|
300
302
|
}')"
|
|
301
303
|
fi
|
|
302
304
|
|
|
305
|
+
# ── Write-on-signal gate ────────────────────────────────────────────────────
|
|
306
|
+
# Agreement: 문서 ≠ 텔레메트리. A no-delta hourly tick must not emit yet another
|
|
307
|
+
# identical meeting doc — it bumps a heartbeat counter instead. A "signal" is an
|
|
308
|
+
# incident, goal drift, owner escalation, a required-execution contract, a handoff
|
|
309
|
+
# that needs execution, or a change in verdict since the last recorded review.
|
|
310
|
+
# Control flow (conductor-tick.sh) routes off progress.json state, not this file,
|
|
311
|
+
# so suppressing the markdown on quiet ticks is safe.
|
|
312
|
+
write_on_signal="$(jq -r '.company_mode.write_on_signal // true' "$CONFIG" 2>/dev/null || echo true)"
|
|
313
|
+
last_verdict="$(jq -r '.meetings.last_reason // ""' "$PROGRESS" 2>/dev/null || echo "")"
|
|
314
|
+
has_signal=true
|
|
315
|
+
if [ "$write_on_signal" = "true" ]; then
|
|
316
|
+
has_signal=false
|
|
317
|
+
case "$verdict" in
|
|
318
|
+
incident|goal_drift|owner_needed) has_signal=true ;;
|
|
319
|
+
esac
|
|
320
|
+
[ "$required_execution_json" != "null" ] && has_signal=true
|
|
321
|
+
[ "$handoff_requires_execution" = "true" ] && has_signal=true
|
|
322
|
+
# A change in verdict since the last recorded review is a state transition worth a
|
|
323
|
+
# minute (this also makes the first-ever review a signal, since last_verdict is "").
|
|
324
|
+
[ "$verdict" != "$last_verdict" ] && has_signal=true
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
if [ "$has_signal" != "true" ]; then
|
|
328
|
+
# No delta — heartbeat only. Keep service-ops health fresh, leave the last real
|
|
329
|
+
# meeting pointer intact (read-decision fallback still resolves), and record that
|
|
330
|
+
# this tick was a quiet checkpoint rather than a meeting.
|
|
331
|
+
jq --arg ts "$ts" --arg verdict "$verdict" '
|
|
332
|
+
.meetings.last_reason = $verdict |
|
|
333
|
+
.meetings.last_review_at = $ts |
|
|
334
|
+
.meetings.heartbeat_count = ((.meetings.heartbeat_count // 0) + 1) |
|
|
335
|
+
.meetings.last_heartbeat_at = $ts |
|
|
336
|
+
.meetings.last_heartbeat_verdict = $verdict |
|
|
337
|
+
.service_ops.monitor.last_hourly_review = $ts
|
|
338
|
+
' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS" || { rm -f "${PROGRESS}.tmp"; exit 1; }
|
|
339
|
+
hb="$(jq -r '.meetings.heartbeat_count // 0' "$PROGRESS" 2>/dev/null || echo '?')"
|
|
340
|
+
echo "$ts | meeting-manager | hourly-heartbeat | $verdict | no-signal (heartbeat #$hb, no meeting doc)" >> "$log_path"
|
|
341
|
+
# stdout: last real meeting path so the wake prompt keeps context (empty if none).
|
|
342
|
+
jq -r '.meetings.last_review_path // empty' "$PROGRESS" 2>/dev/null || true
|
|
343
|
+
exit 0
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
mkdir -p "$meeting_dir"
|
|
347
|
+
|
|
303
348
|
decision_json="$(jq -n \
|
|
304
349
|
--arg owner "$decision_owner" \
|
|
305
350
|
--arg action_type "$decision_action" \
|
package/scripts/harness-wake.sh
CHANGED
|
@@ -130,7 +130,7 @@ start_headless_tick() {
|
|
|
130
130
|
local idle="$2"
|
|
131
131
|
local force="$3"
|
|
132
132
|
local review_path="$4"
|
|
133
|
-
local mode executor wake_model agent_bin stamp
|
|
133
|
+
local mode executor wake_model agent_bin stamp ops_dir activity_dir prompt_scratch log_rel log_path pid_file old_pid pid status tmux_session session_name session_safe wake_prompt log_note
|
|
134
134
|
|
|
135
135
|
if command -v jq >/dev/null 2>&1 && [ -f "$project_root/.harness/config.json" ]; then
|
|
136
136
|
mode="${HARNESS_WAKE_MODE:-$(jq -r '.company_mode.hourly_wake_mode // "headless"' "$project_root/.harness/config.json" 2>/dev/null || echo headless)}"
|
|
@@ -142,13 +142,17 @@ start_headless_tick() {
|
|
|
142
142
|
wake_model="${HARNESS_WAKE_MODEL:-}"
|
|
143
143
|
fi
|
|
144
144
|
stamp="$(date -u "+%Y%m%dT%H%M%SZ")"
|
|
145
|
-
wake_dir="$project_root/.harness/actions/wake"
|
|
146
145
|
ops_dir="$project_root/.harness/ops/wake"
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
activity_dir="$project_root/.harness/activity"
|
|
147
|
+
mkdir -p "$ops_dir" "$activity_dir"
|
|
148
|
+
|
|
149
|
+
# The wake prompt is a static template parameterized only by project/idle/force/
|
|
150
|
+
# review-path. Per the write-on-signal agreement we assemble it in memory rather
|
|
151
|
+
# than persisting a fresh wake-<ts>.prompt.md every tick (those 100+ identical
|
|
152
|
+
# templates were pure noise). tmux mode, which feeds the prompt over stdin, reuses
|
|
153
|
+
# one overwritten scratch file instead of N timestamped ones.
|
|
154
|
+
prompt_scratch="$ops_dir/current.prompt.md"
|
|
150
155
|
log_rel=".harness/ops/wake/wake-${stamp}.log"
|
|
151
|
-
prompt_path="$project_root/$prompt_rel"
|
|
152
156
|
log_path="$project_root/$log_rel"
|
|
153
157
|
pid_file="$ops_dir/wake.pid"
|
|
154
158
|
|
|
@@ -160,7 +164,7 @@ start_headless_tick() {
|
|
|
160
164
|
fi
|
|
161
165
|
fi
|
|
162
166
|
|
|
163
|
-
|
|
167
|
+
wake_prompt="$(cat <<EOF
|
|
164
168
|
You are harness-ceo waking for the walwal-harness hourly executive loop.
|
|
165
169
|
|
|
166
170
|
Project root: $project_root
|
|
@@ -219,7 +223,11 @@ Required final shape for this tick:
|
|
|
219
223
|
- NEXT checkpoint:
|
|
220
224
|
- Evidence paths:
|
|
221
225
|
EOF
|
|
226
|
+
)"
|
|
222
227
|
|
|
228
|
+
# log_note tracks the on-disk artifact for this tick. record mode and
|
|
229
|
+
# BLOCKED_RUNTIME never spawn an agent, so they leave no (previously empty) log.
|
|
230
|
+
log_note="(none)"
|
|
223
231
|
case "$mode" in
|
|
224
232
|
record)
|
|
225
233
|
status="recorded"
|
|
@@ -229,18 +237,19 @@ EOF
|
|
|
229
237
|
(
|
|
230
238
|
cd "$project_root" || exit 1
|
|
231
239
|
if [ "$executor" = "codex" ]; then
|
|
232
|
-
"$agent_bin" exec -C "$project_root" "$
|
|
240
|
+
"$agent_bin" exec -C "$project_root" "$wake_prompt" > "$log_path" 2>&1
|
|
233
241
|
else
|
|
234
242
|
if [ -n "$wake_model" ]; then
|
|
235
|
-
"$agent_bin" -p "$
|
|
243
|
+
"$agent_bin" -p "$wake_prompt" --model "$wake_model" > "$log_path" 2>&1
|
|
236
244
|
else
|
|
237
|
-
"$agent_bin" -p "$
|
|
245
|
+
"$agent_bin" -p "$wake_prompt" > "$log_path" 2>&1
|
|
238
246
|
fi
|
|
239
247
|
fi
|
|
240
248
|
) &
|
|
241
249
|
pid=$!
|
|
242
250
|
echo "$pid" > "$pid_file"
|
|
243
251
|
status="spawned executor=$executor pid=$pid"
|
|
252
|
+
log_note="$log_rel"
|
|
244
253
|
else
|
|
245
254
|
status="BLOCKED_RUNTIME ${executor}-not-found"
|
|
246
255
|
fi
|
|
@@ -249,18 +258,20 @@ EOF
|
|
|
249
258
|
if ! command -v tmux >/dev/null 2>&1; then
|
|
250
259
|
status="BLOCKED_RUNTIME tmux-not-found"
|
|
251
260
|
elif agent_bin="$(resolve_agent_bin "$executor")"; then
|
|
261
|
+
printf '%s\n' "$wake_prompt" > "$prompt_scratch"
|
|
252
262
|
session_safe="$(basename "$project_root" | tr -c '[:alnum:]_' '_')"
|
|
253
263
|
session_name="walwal_${session_safe}_${stamp}"
|
|
254
264
|
if [ "$executor" = "codex" ]; then
|
|
255
|
-
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' exec -C '$project_root' - < '$
|
|
265
|
+
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' exec -C '$project_root' - < '$prompt_scratch' 2>&1 | tee '$log_path'"
|
|
256
266
|
else
|
|
257
267
|
if [ -n "$wake_model" ]; then
|
|
258
|
-
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$
|
|
268
|
+
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$prompt_scratch')\" --model '$wake_model' 2>&1 | tee '$log_path'"
|
|
259
269
|
else
|
|
260
|
-
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$
|
|
270
|
+
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$prompt_scratch')\" 2>&1 | tee '$log_path'"
|
|
261
271
|
fi
|
|
262
272
|
fi
|
|
263
273
|
status="spawned-tmux executor=$executor session=$session_name"
|
|
274
|
+
log_note="$log_rel"
|
|
264
275
|
else
|
|
265
276
|
status="BLOCKED_RUNTIME ${executor}-not-found"
|
|
266
277
|
fi
|
|
@@ -270,13 +281,41 @@ EOF
|
|
|
270
281
|
;;
|
|
271
282
|
esac
|
|
272
283
|
|
|
273
|
-
|
|
274
|
-
|
|
284
|
+
# Per-tick wake telemetry = one params-only line, not a full prompt copy. This
|
|
285
|
+
# is the "if you must keep something, keep one line in activity.jsonl" path from
|
|
286
|
+
# the wake-template agreement, with rotation so it never grows unbounded.
|
|
287
|
+
if command -v jq >/dev/null 2>&1; then
|
|
288
|
+
jq -nc \
|
|
289
|
+
--arg ts "$(date -u "+%Y-%m-%dT%H:%M:%SZ")" \
|
|
290
|
+
--arg stamp "$stamp" \
|
|
291
|
+
--argjson idle "${idle:-0}" \
|
|
292
|
+
--arg force "$force" \
|
|
293
|
+
--arg mode "$mode" \
|
|
294
|
+
--arg executor "$executor" \
|
|
295
|
+
--arg status "$status" \
|
|
296
|
+
--arg review "${review_path:-}" \
|
|
297
|
+
--arg log "$log_note" \
|
|
298
|
+
'{ts:$ts,stamp:$stamp,idle:$idle,force:$force,mode:$mode,executor:$executor,status:$status,review:$review,log:$log}' \
|
|
299
|
+
>> "$activity_dir/wake.jsonl" 2>/dev/null || true
|
|
300
|
+
if [ -f "$activity_dir/wake.jsonl" ] && tail -n 2000 "$activity_dir/wake.jsonl" > "$activity_dir/wake.jsonl.tmp" 2>/dev/null; then
|
|
301
|
+
mv "$activity_dir/wake.jsonl.tmp" "$activity_dir/wake.jsonl"
|
|
302
|
+
else
|
|
303
|
+
rm -f "$activity_dir/wake.jsonl.tmp" 2>/dev/null || true
|
|
304
|
+
fi
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
# Rotate per-tick agent logs: keep only the most recent 48 (~2 days hourly).
|
|
308
|
+
ls -1t "$ops_dir"/wake-*.log 2>/dev/null | tail -n +49 | while IFS= read -r old_log; do
|
|
309
|
+
rm -f "$old_log"
|
|
310
|
+
done
|
|
311
|
+
|
|
312
|
+
echo "$(date -u "+%Y-%m-%dT%H:%M:%SZ") | wake | agent-tick | $status | idle=${idle}s | force=${force} | mode=${mode} | log=$log_note" >> "$project_root/.harness/progress.log"
|
|
313
|
+
say "wake-agent: $project_root (idle ${idle}s, force=${force}, mode=${mode}, executor=${executor}) → $status $log_note"
|
|
275
314
|
|
|
276
315
|
run_conductor_fallback "$project_root" "$status"
|
|
277
316
|
|
|
278
317
|
if tmux_session="$(find_tmux_session "$project_root" 2>/dev/null)"; then
|
|
279
|
-
tmux display-message -t "$tmux_session" "walwal wake headless tick: $status ($
|
|
318
|
+
tmux display-message -t "$tmux_session" "walwal wake headless tick: $status ($log_note)" 2>/dev/null || true
|
|
280
319
|
fi
|
|
281
320
|
}
|
|
282
321
|
|
|
@@ -8,7 +8,8 @@ SCAN_RESULT="${PROJECT_ROOT}/.harness/actions/scan-result.json"
|
|
|
8
8
|
export SCAN_RESULT PROJECT_ROOT
|
|
9
9
|
AGENTS_FILE="${PROJECT_ROOT}/AGENTS.md"
|
|
10
10
|
CLAUDE_FILE="${PROJECT_ROOT}/CLAUDE.md"
|
|
11
|
-
|
|
11
|
+
# Backups live under .harness/backups/, not archive/ (archive = completed missions).
|
|
12
|
+
BACKUP_DIR="${PROJECT_ROOT}/.harness/backups/pre-harness-backup"
|
|
12
13
|
|
|
13
14
|
if [ ! -f "$SCAN_RESULT" ]; then
|
|
14
15
|
echo "ERROR: scan-result.json not found. Run scan-project.sh first."
|
package/scripts/init-ref-docs.sh
CHANGED
|
@@ -44,8 +44,9 @@ done
|
|
|
44
44
|
SCAN="${PROJECT_ROOT}/.harness/actions/scan-result.json"
|
|
45
45
|
REF_DIR="${PROJECT_ROOT}/.harness/ref"
|
|
46
46
|
META="${REF_DIR}/.generated.json"
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
# Ref backups go to .harness/backups/refs, not archive/ (archive = completed missions).
|
|
48
|
+
BACKUP_DIR="${PROJECT_ROOT}/.harness/backups/refs"
|
|
49
|
+
mkdir -p "$REF_DIR" "$BACKUP_DIR"
|
|
49
50
|
[ -f "$META" ] || echo "{}" > "$META"
|
|
50
51
|
|
|
51
52
|
# ───── 감지된 스택 목록 ─────
|
|
@@ -192,9 +193,9 @@ process_one() {
|
|
|
192
193
|
return 0
|
|
193
194
|
fi
|
|
194
195
|
if [ -f "$path" ] && $REFRESH; then
|
|
195
|
-
local backup="${
|
|
196
|
+
local backup="${BACKUP_DIR}/ref-${role}-${stack}-$(date +%Y%m%d%H%M%S).md"
|
|
196
197
|
cp "$path" "$backup"
|
|
197
|
-
echo "
|
|
198
|
+
echo "backup: $backup"
|
|
198
199
|
fi
|
|
199
200
|
|
|
200
201
|
local answer="y"
|