@walwal-harness/cli 7.1.27 → 7.1.29
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/README.md +26 -1
- package/apps/harness-dashboard/app/api/stream/route.ts +4 -0
- package/apps/harness-dashboard/app/globals.css +138 -8
- package/apps/harness-dashboard/app/layout.tsx +1 -5
- package/apps/harness-dashboard/app/page.tsx +3 -7
- package/apps/harness-dashboard/components/Scene.tsx +1788 -599
- package/apps/harness-dashboard/lib/__tests__/harness-state.test.ts +84 -0
- package/apps/harness-dashboard/lib/harness-state.ts +132 -16
- package/apps/harness-dashboard/lib/types.ts +15 -0
- package/apps/harness-dashboard/package-lock.json +1519 -53
- package/apps/harness-dashboard/package.json +2 -0
- package/assets/templates/AGENTS.md.template +6 -5
- package/bin/init.js +273 -41
- package/commands/goal.md +10 -8
- package/commands/hot-fix.md +12 -10
- package/commands/submission.md +12 -9
- package/package.json +1 -1
- package/scripts/harness-company-complete.sh +30 -0
|
@@ -88,7 +88,7 @@ The Owner is the final acceptance reviewer, not a tester, QA worker, debugger, o
|
|
|
88
88
|
4. CEO aggregates CXX outputs, routes to next CXX or reports to Owner.
|
|
89
89
|
5. If the mission uses a runnable local, preview, Docker, or production-like environment, OPS watches the runtime while CQO evaluator workers run Playwright/E2E/API/visual/performance/regression checks.
|
|
90
90
|
|
|
91
|
-
**Complete when:** `.harness/documents/goal-{index}-{name}/ceo.md` contains a final Owner report.
|
|
91
|
+
**Complete when:** `.harness/documents/goal-{index}-{name}/ceo.md` contains a final Owner report and `mission-state.json` is terminal (`complete`, `closed`, `cancelled`, or `superseded`) with `active:false`.
|
|
92
92
|
|
|
93
93
|
### Submission
|
|
94
94
|
|
|
@@ -98,7 +98,7 @@ The Owner is the final acceptance reviewer, not a tester, QA worker, debugger, o
|
|
|
98
98
|
4. Relevant CXX update `.harness/conventions/` when the new requirement changes durable project rules.
|
|
99
99
|
5. Workers report under `{owning-cxx}/workers/`; CXX summarize choices in `{cxx}.md`.
|
|
100
100
|
|
|
101
|
-
**Complete when:** the submission child directory has CEO/CXX records, required worker evidence,
|
|
101
|
+
**Complete when:** the submission child directory has CEO/CXX records, required worker evidence, any convention updates required by the new requirement, and terminal `mission-state.json`.
|
|
102
102
|
|
|
103
103
|
### Hot Fix
|
|
104
104
|
|
|
@@ -150,9 +150,10 @@ Note: A docmeta skip decision on harness documents (ceo.md, cto.md, cqo.md, work
|
|
|
150
150
|
11. **Lazy convention/gotcha loading** — CXX roles read `.harness/conventions/shared.md`, `.harness/conventions/{cxx}.md`, `.harness/gotchas/shared.md`, and `.harness/gotchas/{cxx}.md`, then follow only the related topic links listed in those CXX files. Workers read only the related links supplied by their owning CXX.
|
|
151
151
|
12. **Implementation Notes required** — `ceo.md`, every `{cxx}.md`, and every worker report must end with one English `## Implementation Notes` section containing `Design Decisions`, `Deviations`, `Tradeoffs`, and `Open Questions`. Use `None` for empty subsections. Do not create a separate sidecar notes file; the notes belong at the bottom of the same role or worker report that produced the decision/evidence.
|
|
152
152
|
13. **Structured runtime state** — If the harness must parse it, record it in JSON or JSONL. CXX todo queues, event history, heartbeat timestamps, preemption/resume state, and completion evidence belong in `.harness/todos/*.json*` or `.harness/events.jsonl`, not in free-form Markdown tables. Markdown remains for instructions, conventions, gotchas, skills, and human-readable mission narrative.
|
|
153
|
-
14. **
|
|
154
|
-
15. **
|
|
155
|
-
16. **
|
|
153
|
+
14. **Mission lifecycle is explicit** — Every goal, submission, and hot-fix directory must contain `mission-state.json` with `lifecycle` and `active`. Only one child mission under a goal may be active. Starting a newer submission/hot-fix closes, cancels, or supersedes the previous active child unless CEO records a deliberate TODO/resume plan.
|
|
154
|
+
15. **Owner is final acceptance only** — Owner is not a tester or QA substitute. CEO/CXX must not report "done, please check" until worker-backed verification proves the goal can be completed. Use unit tests, E2E, Playwright, test accounts, seeded data, build/run checks, logs, and CQO evidence before requesting Owner acceptance.
|
|
155
|
+
16. **OPS watches CQO verification** — For runnable products, CQO PASS is invalid unless OPS has monitored the verification runtime or documented why OPS watch is not applicable. Open OPS incidents, missing runtime mapping, required log gaps, service down, health mismatch, or an unmonitored tested service block Owner acceptance.
|
|
156
|
+
17. **Production incidents are company events** — After launch, user-impacting OPS signals route through CEO to CTO/CQO/OPS. CTO owns recovery, CQO owns regression confirmation, and OPS owns evidence plus close criteria.
|
|
156
157
|
|
|
157
158
|
---
|
|
158
159
|
|
package/bin/init.js
CHANGED
|
@@ -112,6 +112,22 @@ function chmodShellScripts(dir) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
function readJsonFileSafe(filePath) {
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readTextFileSafe(filePath) {
|
|
124
|
+
try {
|
|
125
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
115
131
|
function fileExists(p) {
|
|
116
132
|
return fs.existsSync(p);
|
|
117
133
|
}
|
|
@@ -451,6 +467,99 @@ function migrateExistingDocs() {
|
|
|
451
467
|
log(`Migration report: ${reportPath}`);
|
|
452
468
|
}
|
|
453
469
|
|
|
470
|
+
function missionTypeFromDirName(name) {
|
|
471
|
+
if (/^goal[-_]/i.test(name)) return 'goal';
|
|
472
|
+
if (/^submission[-_]/i.test(name)) return 'submission';
|
|
473
|
+
if (/^hot[-_]?fix[-_]/i.test(name) || /^hotfix[-_]/i.test(name)) return 'hotfix';
|
|
474
|
+
return 'unknown';
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function isTerminalMissionLifecycle(value) {
|
|
478
|
+
return ['closed', 'cancelled', 'superseded', 'complete', 'blocked'].includes(String(value || '').toLowerCase());
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function collectMissionDirs(docsDir) {
|
|
482
|
+
const cxxDocs = new Set(['ceo.md', 'coo.md', 'cdo.md', 'cto.md', 'cqo.md', 'ops.md']);
|
|
483
|
+
const result = [];
|
|
484
|
+
const walk = (dir, rel = '') => {
|
|
485
|
+
if (!fs.existsSync(dir)) return;
|
|
486
|
+
let entries = [];
|
|
487
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
488
|
+
const hasMissionDoc = entries.some((entry) => entry.isFile() && cxxDocs.has(entry.name));
|
|
489
|
+
if (rel && hasMissionDoc) {
|
|
490
|
+
let mtime = 0;
|
|
491
|
+
try { mtime = fs.statSync(dir).mtimeMs; } catch {}
|
|
492
|
+
const name = path.basename(rel);
|
|
493
|
+
result.push({ abs: dir, rel, name, type: missionTypeFromDirName(name), mtime });
|
|
494
|
+
}
|
|
495
|
+
for (const entry of entries) {
|
|
496
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
497
|
+
if (['workers', 'coo', 'cdo', 'cto', 'cqo', 'ops'].includes(entry.name)) continue;
|
|
498
|
+
walk(path.join(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
walk(docsDir);
|
|
502
|
+
return result;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function ensureMissionLifecycleStates(opts = {}) {
|
|
506
|
+
const dryRun = opts.dryRun || false;
|
|
507
|
+
const backupDir = opts.backupDir || null;
|
|
508
|
+
const docsDir = path.join(HARNESS_DIR, 'documents');
|
|
509
|
+
if (!fs.existsSync(docsDir)) return { missing: 0, written: 0 };
|
|
510
|
+
|
|
511
|
+
const missions = collectMissionDirs(docsDir);
|
|
512
|
+
if (!missions.length) return { missing: 0, written: 0 };
|
|
513
|
+
|
|
514
|
+
const byGoal = new Map();
|
|
515
|
+
for (const mission of missions) {
|
|
516
|
+
const goalKey = mission.rel.split('/')[0];
|
|
517
|
+
if (!byGoal.has(goalKey)) byGoal.set(goalKey, []);
|
|
518
|
+
byGoal.get(goalKey).push(mission);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const writes = [];
|
|
522
|
+
for (const group of byGoal.values()) {
|
|
523
|
+
const children = group.filter((m) => m.rel.includes('/') && (m.type === 'submission' || m.type === 'hotfix'));
|
|
524
|
+
children.sort((a, b) => b.mtime - a.mtime);
|
|
525
|
+
const existingActiveChild = children.find((m) => {
|
|
526
|
+
const state = readJsonFileSafe(path.join(m.abs, 'mission-state.json'));
|
|
527
|
+
return state?.active === true && !isTerminalMissionLifecycle(state.lifecycle || state.status);
|
|
528
|
+
});
|
|
529
|
+
const activeChild = existingActiveChild || children[0] || null;
|
|
530
|
+
|
|
531
|
+
for (const mission of group) {
|
|
532
|
+
const statePath = path.join(mission.abs, 'mission-state.json');
|
|
533
|
+
if (fs.existsSync(statePath)) continue;
|
|
534
|
+
const isChild = mission.rel.includes('/');
|
|
535
|
+
const isActive = isChild
|
|
536
|
+
? activeChild?.rel === mission.rel
|
|
537
|
+
: true;
|
|
538
|
+
writes.push({
|
|
539
|
+
path: statePath,
|
|
540
|
+
state: {
|
|
541
|
+
lifecycle: isActive ? 'active' : 'closed',
|
|
542
|
+
active: isActive,
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (dryRun) return { missing: writes.length, written: 0 };
|
|
549
|
+
|
|
550
|
+
for (const item of writes) {
|
|
551
|
+
if (backupDir) {
|
|
552
|
+
const rel = path.relative(HARNESS_DIR, item.path).replace(/[\\/]/g, '__');
|
|
553
|
+
if (fs.existsSync(item.path)) fs.writeFileSync(path.join(backupDir, rel), fs.readFileSync(item.path, 'utf8'));
|
|
554
|
+
}
|
|
555
|
+
fs.writeFileSync(item.path, JSON.stringify(item.state, null, 2) + '\n');
|
|
556
|
+
}
|
|
557
|
+
if (writes.length) {
|
|
558
|
+
log(`mission-state.json: ${writes.length}개 기존 mission에 lifecycle 보강`);
|
|
559
|
+
}
|
|
560
|
+
return { missing: writes.length, written: writes.length };
|
|
561
|
+
}
|
|
562
|
+
|
|
454
563
|
// ─────────────────────────────────────────
|
|
455
564
|
// 1. .harness/ scaffolding
|
|
456
565
|
// ─────────────────────────────────────────
|
|
@@ -585,6 +694,7 @@ function scaffoldHarness() {
|
|
|
585
694
|
if (!fileExists(p) || isForce) fs.writeFileSync(p, '');
|
|
586
695
|
}
|
|
587
696
|
ensureStructuredRuntimeFiles();
|
|
697
|
+
ensureMissionLifecycleStates();
|
|
588
698
|
|
|
589
699
|
// Migrate progress.json v1 → v2 (add mode + team_state fields)
|
|
590
700
|
const progressPath = path.join(HARNESS_DIR, 'progress.json');
|
|
@@ -1094,55 +1204,129 @@ function installAgentTeamsEnv() {
|
|
|
1094
1204
|
}
|
|
1095
1205
|
}
|
|
1096
1206
|
|
|
1097
|
-
|
|
1207
|
+
const HARNESS_HEADER_MARKER = '<!-- walwal-harness:managed-header -->';
|
|
1208
|
+
const HARNESS_SECTION_START = '<!-- walwal-harness:agents:start -->';
|
|
1209
|
+
const HARNESS_SECTION_END = '<!-- walwal-harness:agents:end -->';
|
|
1210
|
+
const PRESERVED_CLAUDE_START = '<!-- walwal-harness:preserved-claude:start -->';
|
|
1211
|
+
const PRESERVED_CLAUDE_END = '<!-- walwal-harness:preserved-claude:end -->';
|
|
1212
|
+
|
|
1213
|
+
function backupDocPath(filePath, backupDir, label) {
|
|
1214
|
+
if (!filePath || !fs.existsSync(filePath)) return;
|
|
1215
|
+
ensureDir(backupDir);
|
|
1216
|
+
const safe = label.replace(/[^A-Za-z0-9_.-]/g, '_');
|
|
1217
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1218
|
+
try {
|
|
1219
|
+
fs.copyFileSync(filePath, path.join(backupDir, `${safe}.${ts}.bak`));
|
|
1220
|
+
} catch {}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
function renderHarnessAgentsSection() {
|
|
1224
|
+
const templateSrc = path.join(PKG_ROOT, 'assets', 'templates', 'AGENTS.md.template');
|
|
1225
|
+
let content = fs.existsSync(templateSrc)
|
|
1226
|
+
? fs.readFileSync(templateSrc, 'utf8')
|
|
1227
|
+
: '# walwal-harness\n\nUse `/goal`, `/submission`, and `/hot-fix` for CEO/CXX/worker flow.\n';
|
|
1228
|
+
content = content.replace(/\{\{DATE\}\}/g, new Date().toISOString().split('T')[0]);
|
|
1229
|
+
content = content.replace(/\{\{DATE_ISO\}\}/g, new Date().toISOString());
|
|
1230
|
+
return [
|
|
1231
|
+
HARNESS_SECTION_START,
|
|
1232
|
+
'',
|
|
1233
|
+
content.trim(),
|
|
1234
|
+
'',
|
|
1235
|
+
HARNESS_SECTION_END,
|
|
1236
|
+
''
|
|
1237
|
+
].join('\n');
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
function ensureTopHarnessHeader(body) {
|
|
1241
|
+
if (body.includes(HARNESS_HEADER_MARKER)) return body;
|
|
1242
|
+
const header = [
|
|
1243
|
+
HARNESS_HEADER_MARKER,
|
|
1244
|
+
'> This project is walwal-harness enabled. Harness operating rules live in the marked walwal-harness section below.',
|
|
1245
|
+
''
|
|
1246
|
+
].join('\n');
|
|
1247
|
+
return header + body.replace(/^\s+/, '');
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function upsertMarkedBlock(body, start, end, block) {
|
|
1251
|
+
const startIdx = body.indexOf(start);
|
|
1252
|
+
const endIdx = body.indexOf(end);
|
|
1253
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
1254
|
+
const afterEnd = endIdx + end.length;
|
|
1255
|
+
return body.slice(0, startIdx).replace(/\s*$/, '\n\n') + block.trim() + '\n' + body.slice(afterEnd).replace(/^\s*/, '\n');
|
|
1256
|
+
}
|
|
1257
|
+
const sep = body.trim() ? '\n\n' : '';
|
|
1258
|
+
return body.replace(/\s*$/, '') + sep + block.trim() + '\n';
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
function normalizeDocBody(body) {
|
|
1262
|
+
return String(body || '').replace(/\r\n/g, '\n').trim();
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function ensureProjectAgentDocs(opts = {}) {
|
|
1266
|
+
const dryRun = opts.dryRun || false;
|
|
1267
|
+
const backupDir = opts.backupDir || path.join(HARNESS_DIR, 'archive', 'pre-harness-backup');
|
|
1098
1268
|
const agentsMd = path.join(PROJECT_ROOT, 'AGENTS.md');
|
|
1099
1269
|
const claudeMd = path.join(PROJECT_ROOT, 'CLAUDE.md');
|
|
1100
1270
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1271
|
+
const agentsTarget = fs.existsSync(agentsMd) ? fs.realpathSync(agentsMd) : agentsMd;
|
|
1272
|
+
const existingAgents = readTextFileSafe(agentsMd) || '';
|
|
1273
|
+
const claudeExists = fs.existsSync(claudeMd) || fs.existsSync(claudeMd);
|
|
1274
|
+
const claudeIsSymlink = fs.existsSync(claudeMd) && fs.lstatSync(claudeMd).isSymbolicLink();
|
|
1275
|
+
const claudeTarget = claudeExists ? fs.realpathSync(claudeMd) : null;
|
|
1276
|
+
const claudeBody = claudeExists ? (readTextFileSafe(claudeMd) || '') : '';
|
|
1277
|
+
|
|
1278
|
+
let nextAgents = existingAgents;
|
|
1279
|
+
nextAgents = ensureTopHarnessHeader(nextAgents);
|
|
1280
|
+
nextAgents = upsertMarkedBlock(nextAgents, HARNESS_SECTION_START, HARNESS_SECTION_END, renderHarnessAgentsSection());
|
|
1281
|
+
|
|
1282
|
+
const normalizedClaude = normalizeDocBody(claudeBody);
|
|
1283
|
+
const claudePointsAtAgents = claudeIsSymlink && claudeTarget === agentsTarget;
|
|
1284
|
+
if (normalizedClaude && !claudePointsAtAgents) {
|
|
1285
|
+
const preservedBlock = [
|
|
1286
|
+
PRESERVED_CLAUDE_START,
|
|
1287
|
+
'',
|
|
1288
|
+
'## Preserved CLAUDE.md',
|
|
1289
|
+
'',
|
|
1290
|
+
normalizedClaude,
|
|
1291
|
+
'',
|
|
1292
|
+
PRESERVED_CLAUDE_END,
|
|
1293
|
+
''
|
|
1294
|
+
].join('\n');
|
|
1295
|
+
nextAgents = upsertMarkedBlock(nextAgents, PRESERVED_CLAUDE_START, PRESERVED_CLAUDE_END, preservedBlock);
|
|
1121
1296
|
}
|
|
1122
1297
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1298
|
+
if (dryRun) {
|
|
1299
|
+
log(' (dry-run) AGENTS.md: walwal-harness marked section merge 예정');
|
|
1300
|
+
if (!claudePointsAtAgents) log(' (dry-run) CLAUDE.md: content preserved then symlink → AGENTS.md 예정');
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
if (fs.existsSync(agentsTarget)) backupDocPath(agentsTarget, backupDir, 'AGENTS.md');
|
|
1305
|
+
if (claudeExists && claudeTarget && claudeTarget !== agentsTarget) {
|
|
1306
|
+
backupDocPath(claudeTarget, backupDir, 'CLAUDE.md');
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
ensureDir(path.dirname(agentsMd));
|
|
1310
|
+
fs.writeFileSync(agentsMd, nextAgents.replace(/\s*$/, '\n'));
|
|
1311
|
+
log(fs.existsSync(agentsTarget) ? 'AGENTS.md merged with walwal-harness marked section' : 'AGENTS.md created from walwal-harness template');
|
|
1312
|
+
|
|
1313
|
+
try {
|
|
1314
|
+
if (fs.existsSync(claudeMd) || fs.lstatSync(claudeMd)) fs.unlinkSync(claudeMd);
|
|
1315
|
+
} catch {}
|
|
1316
|
+
try {
|
|
1317
|
+
fs.symlinkSync('AGENTS.md', claudeMd);
|
|
1318
|
+
log('CLAUDE.md replaced with symlink → AGENTS.md');
|
|
1319
|
+
} catch (e) {
|
|
1320
|
+
log(`WARNING: Could not create CLAUDE.md symlink (${e.message})`);
|
|
1143
1321
|
}
|
|
1144
1322
|
}
|
|
1145
1323
|
|
|
1324
|
+
function setupAgentsMd() {
|
|
1325
|
+
const agentsMd = path.join(PROJECT_ROOT, 'AGENTS.md');
|
|
1326
|
+
const claudeMd = path.join(PROJECT_ROOT, 'CLAUDE.md');
|
|
1327
|
+
ensureProjectAgentDocs({ backupDir: path.join(HARNESS_DIR, 'archive', 'pre-harness-backup') });
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1146
1330
|
// ─────────────────────────────────────────
|
|
1147
1331
|
// 5. Playwright MCP check
|
|
1148
1332
|
// ─────────────────────────────────────────
|
|
@@ -1300,6 +1484,8 @@ function detectMigrationNeeded() {
|
|
|
1300
1484
|
harnessMdStale: false,
|
|
1301
1485
|
agentsMissingOpsVerificationRules: false,
|
|
1302
1486
|
agentsMissingCodexAdapterRules: false,
|
|
1487
|
+
agentsMissingMissionLifecycleRules: false,
|
|
1488
|
+
missionLifecycleMissing: false,
|
|
1303
1489
|
rosterCodexPathsMissing: false,
|
|
1304
1490
|
resourceIndexCodexWordingMissing: false,
|
|
1305
1491
|
memoryMissingSystemEntries: [],
|
|
@@ -1427,8 +1613,19 @@ function detectMigrationNeeded() {
|
|
|
1427
1613
|
) {
|
|
1428
1614
|
flags.agentsMissingCodexAdapterRules = true;
|
|
1429
1615
|
}
|
|
1616
|
+
if (
|
|
1617
|
+
!agentsBody.includes('Mission Lifecycle Rules') &&
|
|
1618
|
+
!agentsBody.includes('mission-state.json')
|
|
1619
|
+
) {
|
|
1620
|
+
flags.agentsMissingMissionLifecycleRules = true;
|
|
1621
|
+
}
|
|
1430
1622
|
} catch {}
|
|
1431
1623
|
}
|
|
1624
|
+
const docsDir = path.join(HARNESS_DIR, 'documents');
|
|
1625
|
+
if (fs.existsSync(docsDir)) {
|
|
1626
|
+
flags.missionLifecycleMissing = collectMissionDirs(docsDir)
|
|
1627
|
+
.some((mission) => !fs.existsSync(path.join(mission.abs, 'mission-state.json')));
|
|
1628
|
+
}
|
|
1432
1629
|
if (fs.existsSync(rosterPath)) {
|
|
1433
1630
|
try {
|
|
1434
1631
|
const roster = JSON.parse(fs.readFileSync(rosterPath, 'utf8'));
|
|
@@ -1764,6 +1961,8 @@ function runMigrate(opts = {}) {
|
|
|
1764
1961
|
!flags.agentsMissingOpsVerificationRules &&
|
|
1765
1962
|
!flags.rosterCodexPathsMissing &&
|
|
1766
1963
|
!flags.resourceIndexCodexWordingMissing &&
|
|
1964
|
+
!flags.agentsMissingMissionLifecycleRules &&
|
|
1965
|
+
!flags.missionLifecycleMissing &&
|
|
1767
1966
|
(!flags.memoryMissingSystemEntries || flags.memoryMissingSystemEntries.length === 0) &&
|
|
1768
1967
|
gotchaMissingTotal === 0 &&
|
|
1769
1968
|
conventionMissingTotal === 0 &&
|
|
@@ -1785,6 +1984,14 @@ function runMigrate(opts = {}) {
|
|
|
1785
1984
|
const backupDir = path.join(HARNESS_DIR, 'archive', `migration-${ts}`);
|
|
1786
1985
|
if (!dryRun) ensureDir(backupDir);
|
|
1787
1986
|
ensureStructuredRuntimeFiles({ dryRun });
|
|
1987
|
+
ensureProjectAgentDocs({ dryRun, backupDir });
|
|
1988
|
+
flags.agentsMissingOpsVerificationRules = false;
|
|
1989
|
+
flags.agentsMissingCodexAdapterRules = false;
|
|
1990
|
+
flags.agentsMissingMissionLifecycleRules = false;
|
|
1991
|
+
const missionStateResult = ensureMissionLifecycleStates({ dryRun, backupDir });
|
|
1992
|
+
if (dryRun && missionStateResult.missing > 0) {
|
|
1993
|
+
log(` (dry-run) mission-state.json: ${missionStateResult.missing}개 기존 mission에 lifecycle 보강 예정`);
|
|
1994
|
+
}
|
|
1788
1995
|
|
|
1789
1996
|
// Runtime scripts are package-owned. Migrate must refresh them so existing
|
|
1790
1997
|
// projects receive wake/meeting/OPS fixes without requiring a full init.
|
|
@@ -2099,6 +2306,31 @@ function runMigrate(opts = {}) {
|
|
|
2099
2306
|
}
|
|
2100
2307
|
}
|
|
2101
2308
|
|
|
2309
|
+
if (flags.agentsMissingMissionLifecycleRules) {
|
|
2310
|
+
const agentsPath = path.join(PROJECT_ROOT, 'AGENTS.md');
|
|
2311
|
+
if (fs.existsSync(agentsPath)) {
|
|
2312
|
+
const original = fs.readFileSync(agentsPath, 'utf8');
|
|
2313
|
+
const block = [
|
|
2314
|
+
'',
|
|
2315
|
+
'---',
|
|
2316
|
+
'',
|
|
2317
|
+
'## walwal-harness Mission Lifecycle Rules — v7.1.28',
|
|
2318
|
+
'',
|
|
2319
|
+
'- Every goal, submission, and hot-fix directory must contain `mission-state.json` with `lifecycle` and `active`.',
|
|
2320
|
+
'- Only one child mission under a goal may be active. Starting a newer submission/hot-fix closes, cancels, or supersedes the previous active child unless CEO records a deliberate TODO/resume plan.',
|
|
2321
|
+
'- CXX completion requires a Worker Evidence Manifest plus worker report paths under `{owning-cxx}/workers/`. A CXX report without hired worker evidence is a protocol violation.',
|
|
2322
|
+
'- Worker execution should be shown as worker activity; CXX activity is coordination only.',
|
|
2323
|
+
''
|
|
2324
|
+
].join('\n');
|
|
2325
|
+
log(' AGENTS.md: mission lifecycle / CXX hire-only rules append');
|
|
2326
|
+
if (!dryRun) {
|
|
2327
|
+
fs.writeFileSync(path.join(backupDir, 'AGENTS.mission-lifecycle.md'), original);
|
|
2328
|
+
const sep = original.endsWith('\n') ? '' : '\n';
|
|
2329
|
+
fs.writeFileSync(agentsPath, original + sep + block);
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2102
2334
|
// 7. Bundle version stamp — record which package version was last applied.
|
|
2103
2335
|
if (flags.bundleVersionStale) {
|
|
2104
2336
|
const { current, installed } = flags.bundleVersionStale;
|
package/commands/goal.md
CHANGED
|
@@ -14,14 +14,16 @@ Codex adapter:
|
|
|
14
14
|
|
|
15
15
|
Required flow:
|
|
16
16
|
1. Create or update a goal under `.harness/documents/goal-{goal_index}-{goal_name}/`.
|
|
17
|
-
2.
|
|
18
|
-
3. CEO
|
|
19
|
-
4. CEO
|
|
20
|
-
5.
|
|
21
|
-
6. CXX roles must
|
|
22
|
-
7.
|
|
23
|
-
8.
|
|
24
|
-
9.
|
|
17
|
+
2. Write `.harness/documents/goal-{goal_index}-{goal_name}/mission-state.json` with `{"lifecycle":"active","active":true}` before routing CXX.
|
|
18
|
+
3. Record CEO decisions in `.harness/documents/goal-{goal_index}-{goal_name}/ceo.md`.
|
|
19
|
+
4. CEO decides whether brainstorming is needed or whether CXX questions can be issued immediately.
|
|
20
|
+
5. CEO asks COO, CDO, CTO, and CQO only the questions needed for this goal.
|
|
21
|
+
6. CXX roles must start in a fresh session context. Do not let the default model impersonate a missing worker.
|
|
22
|
+
7. CXX roles must not directly execute specialist deliverables. They must use hired workers for research, planning, design, implementation, QA, ops checks, and documentation.
|
|
23
|
+
8. If a required CXX or worker skill is missing, CEO must invoke the installed `harness-hiring` skill before assigning the work.
|
|
24
|
+
9. CEO must require a Worker Evidence Manifest and worker report paths under `.harness/documents/goal-{goal_index}-{goal_name}/{owning-cxx}/workers/` before accepting CXX completion.
|
|
25
|
+
10. When the goal is accepted, cancelled, superseded, blocked, or closed, update `mission-state.json` to `complete`, `cancelled`, `superseded`, `blocked`, or `closed` and set `active:false`.
|
|
26
|
+
11. Do not invoke internal roles through slash commands; commands are Owner entrypoints only.
|
|
25
27
|
|
|
26
28
|
Note: A goal is the company's objective. Submissions and hot-fixes that happen while pursuing it should be recorded under that goal directory.
|
|
27
29
|
|
package/commands/hot-fix.md
CHANGED
|
@@ -14,16 +14,18 @@ Codex adapter:
|
|
|
14
14
|
|
|
15
15
|
Required flow:
|
|
16
16
|
1. Locate the active goal document root under `.harness/documents/{goal_name}/`. If no active goal exists, CEO must create/select one before proceeding.
|
|
17
|
-
2.
|
|
18
|
-
3.
|
|
19
|
-
4.
|
|
20
|
-
5.
|
|
21
|
-
6.
|
|
22
|
-
7.
|
|
23
|
-
8.
|
|
24
|
-
9.
|
|
25
|
-
10.
|
|
26
|
-
11.
|
|
17
|
+
2. If another child mission under the goal is active, update its `mission-state.json` to `closed`, `cancelled`, or `superseded` with `active:false` before starting the hot-fix.
|
|
18
|
+
3. Create `.harness/documents/{goal_name}/hotfix-{hotfix_index}-{hotfix_name}/ceo.md`.
|
|
19
|
+
4. Write `.harness/documents/{goal_name}/hotfix-{hotfix_index}-{hotfix_name}/mission-state.json` with `{"lifecycle":"active","active":true}`.
|
|
20
|
+
5. CEO summons CTO and CQO first; summon COO/CDO only when planning or UX decisions are involved.
|
|
21
|
+
6. CTO applies the smallest correct patch path through hired implementation skills.
|
|
22
|
+
7. CXX roles must start in a fresh session context. Do not let the default model impersonate a missing CXX or worker.
|
|
23
|
+
8. CXX roles must not directly execute specialist deliverables. They must use hired workers for implementation, QA, ops checks, and documentation even when the fix is small.
|
|
24
|
+
9. If a required worker is missing, CEO or the responsible CXX must invoke the installed `harness-hiring` skill before assigning the work.
|
|
25
|
+
10. CEO must require a Worker Evidence Manifest and worker report paths under `.harness/documents/{goal_name}/hotfix-{hotfix_index}-{hotfix_name}/{owning-cxx}/workers/` before accepting CXX completion.
|
|
26
|
+
11. CQO must register durable lessons in `.harness/gotchas/`, `.harness/conventions/`, or `.harness/memories/`. This step is mandatory, not optional, even for small fixes.
|
|
27
|
+
12. Archive only after CQO has accepted the fix, then update `mission-state.json` to `complete` with `active:false`.
|
|
28
|
+
13. Do not invoke internal roles through slash commands; commands are Owner entrypoints only.
|
|
27
29
|
|
|
28
30
|
Note: `/hot-fix` is a problem-fix flow while pursuing the active goal. It belongs under that goal in history.
|
|
29
31
|
|
package/commands/submission.md
CHANGED
|
@@ -14,15 +14,18 @@ Codex adapter:
|
|
|
14
14
|
|
|
15
15
|
Required flow:
|
|
16
16
|
1. Locate the active goal document root under `.harness/documents/{goal_name}/`. If no active goal exists, CEO must ask the Owner to create or select one with `/goal` before proceeding.
|
|
17
|
-
2.
|
|
18
|
-
3.
|
|
19
|
-
4.
|
|
20
|
-
5.
|
|
21
|
-
6.
|
|
22
|
-
7.
|
|
23
|
-
8. CXX must
|
|
24
|
-
9.
|
|
25
|
-
10.
|
|
17
|
+
2. If another child mission under the goal is active, update its `mission-state.json` to `closed`, `cancelled`, or `superseded` with `active:false` before starting this submission.
|
|
18
|
+
3. Create a submission record under `.harness/documents/{goal_name}/submission-{submission_index}-{submission_name}/`.
|
|
19
|
+
4. Write `.harness/documents/{goal_name}/submission-{submission_index}-{submission_name}/mission-state.json` with `{"lifecycle":"active","active":true}`.
|
|
20
|
+
5. Record CEO decisions in `.harness/documents/{goal_name}/submission-{submission_index}-{submission_name}/ceo.md`.
|
|
21
|
+
6. CEO routes only to the CXX agents needed for this additional requirement.
|
|
22
|
+
7. CXX roles must start in a fresh session context. Do not let the default model impersonate a missing CXX or worker.
|
|
23
|
+
8. CXX roles must not directly execute specialist deliverables. They must use hired workers for research, planning, design, implementation, QA, ops checks, and documentation.
|
|
24
|
+
9. If a required worker is missing, the responsible CXX must invoke the installed `harness-hiring` skill before assigning the work.
|
|
25
|
+
10. CXX must manage conventions for new requirements. Durable changes in behavior, architecture, UI, process, or policy must be reflected in `.harness/conventions/` when accepted.
|
|
26
|
+
11. CEO must require a Worker Evidence Manifest and worker report paths under `.harness/documents/{goal_name}/submission-{submission_index}-{submission_name}/{owning-cxx}/workers/` before accepting CXX completion.
|
|
27
|
+
12. When the submission is accepted, cancelled, superseded, blocked, or closed, update `mission-state.json` to `complete`, `cancelled`, `superseded`, `blocked`, or `closed` and set `active:false`.
|
|
28
|
+
13. Do not invoke internal roles through slash commands; commands are Owner entrypoints only.
|
|
26
29
|
|
|
27
30
|
Note: `/submission` is not a new company goal and not an emergency fix. It is an additional requirement while pursuing the active goal. It belongs under that goal in history.
|
|
28
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.29",
|
|
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"
|
|
@@ -15,6 +15,7 @@ PROJECT_ROOT="${1:-.}"
|
|
|
15
15
|
REASON="${2:-mission-complete}"
|
|
16
16
|
|
|
17
17
|
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
18
|
+
TODOS="$PROJECT_ROOT/.harness/todos/state.json"
|
|
18
19
|
[ -f "$PROGRESS" ] || {
|
|
19
20
|
echo "[company-complete] not found: $PROGRESS" >&2
|
|
20
21
|
exit 1
|
|
@@ -40,4 +41,33 @@ bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" \
|
|
|
40
41
|
.owner_prompt.status = "completed" |
|
|
41
42
|
.owner_prompt.completed_at = (now | todate)'
|
|
42
43
|
|
|
44
|
+
if [ -f "$TODOS" ]; then
|
|
45
|
+
tmp="$(mktemp)"
|
|
46
|
+
jq '
|
|
47
|
+
.owners |= with_entries(
|
|
48
|
+
.value |= map(
|
|
49
|
+
if (.status == "active" or .status == "pending" or .status == "paused")
|
|
50
|
+
then .status = "done" | .updated_at = (now | todate)
|
|
51
|
+
else .
|
|
52
|
+
end
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
' "$TODOS" > "$tmp" && mv "$tmp" "$TODOS"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
DOCS="$PROJECT_ROOT/.harness/documents"
|
|
59
|
+
if [ -d "$DOCS" ]; then
|
|
60
|
+
find "$DOCS" -name mission-state.json -type f -print0 | while IFS= read -r -d '' state; do
|
|
61
|
+
active="$(jq -r '.active // false' "$state" 2>/dev/null || echo false)"
|
|
62
|
+
lifecycle="$(jq -r '.lifecycle // .status // "unknown"' "$state" 2>/dev/null || echo unknown)"
|
|
63
|
+
case "$lifecycle" in
|
|
64
|
+
closed|cancelled|superseded|complete|blocked) continue ;;
|
|
65
|
+
esac
|
|
66
|
+
if [ "$active" = "true" ]; then
|
|
67
|
+
tmp="$(mktemp)"
|
|
68
|
+
jq '.lifecycle = "complete" | .active = false | .completed_at = (now | todate)' "$state" > "$tmp" && mv "$tmp" "$state"
|
|
69
|
+
fi
|
|
70
|
+
done
|
|
71
|
+
fi
|
|
72
|
+
|
|
43
73
|
echo "[company-complete] marked complete: $REASON"
|