codex-genesis-harness 0.1.6 → 0.1.8

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.
Files changed (136) hide show
  1. package/.codebase/COMPRESSED_CONTEXT.md +80 -0
  2. package/.codebase/CURRENT_STATE.md +35 -8
  3. package/.codebase/DEPENDENCY_GRAPH.md +14 -1
  4. package/.codebase/IMPLEMENTATION_HANDOFF.md +34 -336
  5. package/.codebase/KNOWN_PROBLEMS.md +54 -3
  6. package/.codebase/MODULE_INDEX.md +8 -0
  7. package/.codebase/PIPELINE_FLOW.md +7 -5
  8. package/.codebase/RECOVERY_POINTS.md +15 -431
  9. package/.codebase/TECH_DEBT.md +6 -0
  10. package/.codebase/TEST_MATRIX.md +4 -3
  11. package/.codebase/VISUAL_GRAPH.md +127 -0
  12. package/.codebase/beads.json +16 -0
  13. package/.codebase/context-policy.json +68 -0
  14. package/.codebase/memories/lessons_learned.md +21 -0
  15. package/.codebase/memories/preferences.md +17 -0
  16. package/.codebase/state.json +45 -24
  17. package/.codex/skills/genesis-ai-provider/SKILL.md +1 -1
  18. package/.codex/skills/genesis-api-contract/SKILL.md +1 -1
  19. package/.codex/skills/genesis-api-sync/SKILL.md +1 -1
  20. package/.codex/skills/genesis-architecture/SKILL.md +6 -1
  21. package/.codex/skills/genesis-codebase-map/SKILL.md +1 -1
  22. package/.codex/skills/genesis-debug-guide/SKILL.md +11 -5
  23. package/.codex/skills/genesis-design-spec/SKILL.md +3 -3
  24. package/.codex/skills/genesis-docs-automation/SKILL.md +52 -973
  25. package/.codex/skills/genesis-executing-plans/SKILL.md +54 -0
  26. package/.codex/skills/genesis-executing-plans/agents/openai.yaml +6 -0
  27. package/.codex/skills/genesis-executing-plans/checklists/.gitkeep +0 -0
  28. package/.codex/skills/genesis-executing-plans/examples/.gitkeep +0 -0
  29. package/.codex/skills/genesis-executing-plans/templates/.gitkeep +0 -0
  30. package/.codex/skills/genesis-harness/SKILL.md +64 -1384
  31. package/.codex/skills/genesis-harness/scripts/check-docs-sync.sh +3 -3
  32. package/.codex/skills/genesis-harness/scripts/init-planning.sh +1 -1
  33. package/.codex/skills/genesis-harness-engineering/SKILL.md +1 -1
  34. package/.codex/skills/genesis-new-design/SKILL.md +6 -2
  35. package/.codex/skills/genesis-new-design/agents/openai.yaml +2 -0
  36. package/.codex/skills/genesis-observability-automation/SKILL.md +69 -303
  37. package/.codex/skills/genesis-observability-automation/references/common-mistakes-and-recovery.md +84 -0
  38. package/.codex/skills/genesis-observability-automation/references/workflow-phases.md +78 -0
  39. package/.codex/skills/genesis-performance-profiling/SKILL.md +1 -22
  40. package/.codex/skills/genesis-performance-profiling/agents/openai.yaml +1 -1
  41. package/.codex/skills/genesis-pipeline-orchestration/SKILL.md +1 -1
  42. package/.codex/skills/genesis-planning/SKILL.md +31 -1
  43. package/.codex/skills/genesis-release/SKILL.md +29 -1
  44. package/.codex/skills/genesis-research-first/SKILL.md +6 -0
  45. package/.codex/skills/genesis-spec-propagation/SKILL.md +52 -504
  46. package/.codex/skills/genesis-test-driven-development/SKILL.md +55 -0
  47. package/.codex/skills/genesis-test-driven-development/agents/openai.yaml +6 -0
  48. package/.codex/skills/genesis-test-driven-development/checklists/.gitkeep +0 -0
  49. package/.codex/skills/genesis-test-driven-development/examples/.gitkeep +0 -0
  50. package/.codex/skills/genesis-test-driven-development/templates/.gitkeep +0 -0
  51. package/.codex/skills/{ui-ux-test-skill → genesis-ui-ux-test}/SKILL.md +1 -1
  52. package/.codex/skills/genesis-upgrade-design/SKILL.md +4 -2
  53. package/.codex/skills/genesis-upgrade-design/agents/openai.yaml +2 -0
  54. package/.codex/skills/genesis-using-git-worktrees/SKILL.md +54 -0
  55. package/.codex/skills/genesis-using-git-worktrees/agents/openai.yaml +6 -0
  56. package/.codex/skills/genesis-using-git-worktrees/checklists/.gitkeep +0 -0
  57. package/.codex/skills/genesis-using-git-worktrees/examples/.gitkeep +0 -0
  58. package/.codex/skills/genesis-using-git-worktrees/templates/.gitkeep +0 -0
  59. package/.codex/skills/genesis-verification-before-completion/SKILL.md +53 -0
  60. package/.codex/skills/genesis-verification-before-completion/agents/openai.yaml +6 -0
  61. package/.codex/skills/genesis-verification-before-completion/checklists/.gitkeep +0 -0
  62. package/.codex/skills/genesis-verification-before-completion/examples/.gitkeep +0 -0
  63. package/.codex/skills/genesis-verification-before-completion/templates/.gitkeep +0 -0
  64. package/.codex/skills/spec-impact-engine/SKILL.md +77 -500
  65. package/.codex/skills/spec-impact-engine/checklists/checklist.md +10 -0
  66. package/.codex-plugin/plugin.json +3 -4
  67. package/CHANGELOG.md +17 -0
  68. package/README.EN.md +33 -22
  69. package/README.VI.md +36 -24
  70. package/README.md +46 -8
  71. package/VERSION +1 -1
  72. package/bin/genesis-harness.js +1337 -7
  73. package/contracts/features/registry-schema.json +15 -0
  74. package/contracts/observability/agent-run-schema.json +34 -0
  75. package/contracts/observability/failure-schema.json +35 -0
  76. package/contracts/ui/auth/login-screen-contract.json +43 -0
  77. package/features/REGISTRY.md +63 -0
  78. package/features/SCOPE-template.md +65 -0
  79. package/fixtures/planning/MOCKUP_PROMPT_TEMPLATE.md +16 -0
  80. package/observability/agent-runs/sample-run.json +13 -0
  81. package/observability/decision-logs/sample-decision.md +43 -0
  82. package/observability/failures/sample-failure.json +12 -0
  83. package/package.json +9 -3
  84. package/playwright/e2e/app-template.spec.js +37 -0
  85. package/playwright/e2e/auth/login-screen.spec.js +65 -0
  86. package/playwright/e2e/web-template.spec.js +28 -0
  87. package/scripts/check-scope.sh +100 -0
  88. package/scripts/cold-start-check.js +133 -0
  89. package/scripts/install.sh +6 -6
  90. package/scripts/prompt_sentinel.js +35 -4
  91. package/scripts/run-evals.sh +137 -26
  92. package/scripts/scratch_parser.js +49 -0
  93. package/scripts/spec_visual_sync.js +1 -1
  94. package/scripts/test_generator.js +2 -2
  95. package/scripts/uninstall.sh +6 -6
  96. package/scripts/verify.sh +21 -66
  97. package/tests/integration/cli-smoke.test.js +103 -0
  98. package/tests/unit/feature_registry.test.js +152 -0
  99. package/tests/unit/prompt_sentinel.test.js +1 -1
  100. package/tests/unit/spec_visual_sync.test.js +1 -1
  101. package/tests/unit/test_generator.test.js +1 -1
  102. package/.codex/skills/genesis-docs/SKILL.md +0 -46
  103. package/.codex/skills/genesis-docs/agents/openai.yaml +0 -7
  104. package/.codex/skills/genesis-mvp-planning/SKILL.md +0 -114
  105. package/.codex/skills/genesis-mvp-planning/agents/openai.yaml +0 -6
  106. package/.codex/skills/genesis-release-orchestration/SKILL.md +0 -653
  107. package/.codex/skills/genesis-release-orchestration/agents/openai.yaml +0 -7
  108. package/.codex/skills/genesis-research/SKILL.md +0 -46
  109. package/.codex/skills/genesis-research/agents/openai.yaml +0 -7
  110. package/playwright/e2e/e2e-template.md +0 -4
  111. /package/.codex/skills/{genesis-docs/checklists/checklist.md → genesis-docs-automation/checklists/manual-docs-checklist.md} +0 -0
  112. /package/.codex/skills/{genesis-docs/examples/example.md → genesis-docs-automation/examples/manual-docs-example.md} +0 -0
  113. /package/.codex/skills/{genesis-docs → genesis-docs-automation}/templates/docs-update-template.md +0 -0
  114. /package/.codex/skills/{genesis-state-machine/SKILL.md → genesis-harness/references/state-machine.md} +0 -0
  115. /package/.codex/skills/{genesis-mvp-planning → genesis-planning}/checklists/mvp-readiness.md +0 -0
  116. /package/.codex/skills/{genesis-mvp-planning → genesis-planning}/examples/5-phase-roadmap-example.md +0 -0
  117. /package/.codex/skills/{genesis-mvp-planning → genesis-planning}/templates/phase-1-core.md +0 -0
  118. /package/.codex/skills/{genesis-mvp-planning → genesis-planning}/templates/phase-2-auth.md +0 -0
  119. /package/.codex/skills/{genesis-mvp-planning → genesis-planning}/templates/phase-3-features.md +0 -0
  120. /package/.codex/skills/{genesis-mvp-planning → genesis-planning}/templates/phase-4-integrations.md +0 -0
  121. /package/.codex/skills/{genesis-mvp-planning → genesis-planning}/templates/phase-5-readiness.md +0 -0
  122. /package/.codex/skills/{genesis-release-orchestration → genesis-release}/checklists/post-deployment-verification.md +0 -0
  123. /package/.codex/skills/{genesis-release-orchestration → genesis-release}/checklists/pre-release-validation.md +0 -0
  124. /package/.codex/skills/{genesis-release-orchestration/examples/example.md → genesis-release/examples/orchestration-example.md} +0 -0
  125. /package/.codex/skills/{genesis-release-orchestration → genesis-release}/observability/release-tracking.md +0 -0
  126. /package/.codex/skills/{genesis-release-orchestration → genesis-release}/playbooks/canary-deployment-orchestration.md +0 -0
  127. /package/.codex/skills/{genesis-release-orchestration → genesis-release}/playbooks/semantic-versioning-automation.md +0 -0
  128. /package/.codex/skills/{genesis-release-orchestration → genesis-release}/templates/deployment-strategy-template.md +0 -0
  129. /package/.codex/skills/{genesis-release-orchestration → genesis-release}/templates/release-runbook-template.md +0 -0
  130. /package/.codex/skills/{genesis-research → genesis-research-first}/checklists/checklist.md +0 -0
  131. /package/.codex/skills/{genesis-research/examples/example.md → genesis-research-first/examples/manual-research-example.md} +0 -0
  132. /package/.codex/skills/{genesis-research → genesis-research-first}/templates/research-note-template.md +0 -0
  133. /package/.codex/skills/{ui-ux-test-skill → genesis-ui-ux-test}/agents/openai.yaml +0 -0
  134. /package/.codex/skills/{ui-ux-test-skill → genesis-ui-ux-test}/checklists/checklist.md +0 -0
  135. /package/.codex/skills/{ui-ux-test-skill → genesis-ui-ux-test}/examples/example.md +0 -0
  136. /package/.codex/skills/{ui-ux-test-skill → genesis-ui-ux-test}/templates/playwright-test-template.md +0 -0
@@ -12,26 +12,26 @@ const skillNames = [
12
12
  "genesis-upgrade-design",
13
13
  "genesis-architecture",
14
14
  "genesis-planning",
15
- "genesis-mvp-planning",
16
15
  "genesis-codebase-map",
17
16
  "genesis-design-spec",
18
17
  "genesis-api-contract",
19
- "ui-ux-test-skill",
18
+ "genesis-ui-ux-test",
20
19
  "genesis-harness-engineering",
21
20
  "genesis-ai-provider",
22
21
  "genesis-pipeline-orchestration",
23
- "genesis-research",
24
- "genesis-docs",
25
- "genesis-release",
26
22
  "genesis-api-sync",
27
23
  "genesis-debug-guide",
28
24
  "genesis-docs-automation",
29
25
  "genesis-spec-propagation",
30
- "genesis-release-orchestration",
31
26
  "genesis-performance-profiling",
32
27
  "genesis-observability-automation",
33
28
  "genesis-research-first",
34
- "spec-impact-engine"
29
+ "genesis-release",
30
+ "spec-impact-engine",
31
+ "genesis-executing-plans",
32
+ "genesis-test-driven-development",
33
+ "genesis-verification-before-completion",
34
+ "genesis-using-git-worktrees"
35
35
  ];
36
36
  const legacySkillNames = ["project-genesis-harness"];
37
37
  const sourceRoot = path.join(packageRoot, ".codex", "skills");
@@ -49,11 +49,28 @@ Usage:
49
49
  genesis-harness verify [--target agents|legacy|both]
50
50
  genesis-harness uninstall [--target agents|legacy|both]
51
51
  genesis-harness path
52
+ genesis-harness status Show implementation status & skills inventory
53
+ genesis-harness docs Show API contracts & documentation sync report
54
+ genesis-harness docs-gate Run pre-commit documentation drift checks
55
+ genesis-harness verify-gate Run ALL verification gates before claiming done (L09 blocker)
56
+ genesis-harness cold-start Run automated cold-start L03 checklist
57
+ genesis-harness remember [cat] "<msg>" Remember a persistent project fact/insight (Bead)
58
+ genesis-harness recall [query] Recall and search remembered project facts
59
+ genesis-harness forget <id> Forget/delete a fact by its unique 6-char ID
60
+ genesis-harness prime Generate the token-minimized Agent Priming Prompt
61
+ genesis-harness leanctx Show token budget policy and portable command guidance
62
+ genesis-harness view-mockup [slug] Interactive console UI to search & view mockups
63
+ genesis-harness mcp Interactive MCP installer
64
+ genesis-harness sync Compress and sync codebase context (AST/Regex)
65
+ genesis-harness setup-hooks Install auto-sync git pre-commit hook
66
+ genesis-harness heal <command> Run test & print agent directive on failure
52
67
 
53
68
  Environment:
54
69
  CODEX_HOME=/custom/.codex Override Codex home
55
70
  GENESIS_HARNESS_HOME=/custom/.agents Override modern skills home
56
71
  GENESIS_HARNESS_SKIP_POSTINSTALL=1 Skip npm postinstall auto-install
72
+ GENESIS_HARNESS_COMMAND_WRAPPER=rtk Optional local command wrapper override
73
+ GENESIS_HARNESS_DISABLE_RTK=1 Disable automatic rtk detection
57
74
  `;
58
75
  console.log(text.trim());
59
76
  process.exit(exitCode);
@@ -119,6 +136,49 @@ function copySkills({ quiet = false, target = "both" } = {}) {
119
136
  if (!quiet) console.log("Restart Codex, then invoke: Use $genesis-harness");
120
137
  }
121
138
 
139
+ function shouldSeedProjectRoot(rootPath) {
140
+ if (!rootPath) return false;
141
+ const resolvedRoot = path.resolve(rootPath);
142
+ if (resolvedRoot === packageRoot) return false;
143
+ const markers = [
144
+ "package.json",
145
+ "AGENTS.md",
146
+ "pyproject.toml",
147
+ "Cargo.toml",
148
+ "go.mod",
149
+ ".git",
150
+ ".codebase"
151
+ ];
152
+ return markers.some(marker => fs.existsSync(path.join(resolvedRoot, marker)));
153
+ }
154
+
155
+ function seedLeanCtxPolicy(rootPath = process.cwd(), { quiet = false } = {}) {
156
+ if (!shouldSeedProjectRoot(rootPath)) return false;
157
+
158
+ const sourcePolicy = path.join(packageRoot, ".codebase", "context-policy.json");
159
+ if (!fs.existsSync(sourcePolicy)) return false;
160
+
161
+ const codebaseDir = path.join(rootPath, ".codebase");
162
+ const targetPolicy = path.join(codebaseDir, "context-policy.json");
163
+ fs.mkdirSync(codebaseDir, { recursive: true });
164
+
165
+ if (fs.existsSync(targetPolicy)) {
166
+ if (!quiet) console.log(`[genesis-harness] LeanCTX policy already exists: ${targetPolicy}`);
167
+ return false;
168
+ }
169
+
170
+ fs.copyFileSync(sourcePolicy, targetPolicy);
171
+ if (!quiet) console.log(`[genesis-harness] LeanCTX policy installed: ${targetPolicy}`);
172
+ return true;
173
+ }
174
+
175
+ function resolvePostinstallProjectRoot() {
176
+ const initCwd = process.env.INIT_CWD;
177
+ if (shouldSeedProjectRoot(initCwd)) return initCwd;
178
+ if (shouldSeedProjectRoot(process.cwd())) return process.cwd();
179
+ return null;
180
+ }
181
+
122
182
  function uninstallSkills(target = "both") {
123
183
  for (const root of targetRoots(target)) {
124
184
  for (const skillName of [...skillNames, ...legacySkillNames]) {
@@ -162,6 +222,119 @@ function resolveBash() {
162
222
  return "bash";
163
223
  }
164
224
 
225
+ function commandExists(commandName) {
226
+ if (!/^[A-Za-z0-9._-]+$/.test(commandName)) return false;
227
+ const result = process.platform === "win32"
228
+ ? spawnSync("where", [commandName], { stdio: "ignore" })
229
+ : spawnSync("sh", ["-c", `command -v ${commandName}`], { stdio: "ignore" });
230
+ return result.status === 0;
231
+ }
232
+
233
+ function detectCommandWrapper() {
234
+ if (process.env.GENESIS_HARNESS_COMMAND_WRAPPER) {
235
+ return {
236
+ command: process.env.GENESIS_HARNESS_COMMAND_WRAPPER,
237
+ source: "GENESIS_HARNESS_COMMAND_WRAPPER"
238
+ };
239
+ }
240
+
241
+ if (process.env.GENESIS_HARNESS_DISABLE_RTK === "1") {
242
+ return { command: null, source: "disabled" };
243
+ }
244
+
245
+ if (commandExists("rtk")) {
246
+ return { command: "rtk", source: "auto-detected" };
247
+ }
248
+
249
+ return { command: null, source: "not detected" };
250
+ }
251
+
252
+ function readJsonIfExists(filePath) {
253
+ if (!fs.existsSync(filePath)) return null;
254
+ try {
255
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
256
+ } catch (error) {
257
+ return null;
258
+ }
259
+ }
260
+
261
+ function defaultContextPolicy() {
262
+ return {
263
+ name: "leanctx-default",
264
+ token_budget: 12000,
265
+ warn_at: 0.6,
266
+ compact_at: 0.7,
267
+ hard_stop_at: 0.85,
268
+ portable_commands: [
269
+ "genesis-harness leanctx",
270
+ "genesis-harness sync",
271
+ "genesis-harness docs-gate",
272
+ "npm run verify",
273
+ "npm run eval"
274
+ ],
275
+ wrapper_policy: "rtk optional when installed locally; public docs and CI must use portable commands.",
276
+ layers: []
277
+ };
278
+ }
279
+
280
+ function loadContextPolicy(rootPath = process.cwd()) {
281
+ const projectPolicy = readJsonIfExists(path.join(rootPath, ".codebase", "context-policy.json"));
282
+ const packagedPolicy = readJsonIfExists(path.join(packageRoot, ".codebase", "context-policy.json"));
283
+ return {
284
+ ...defaultContextPolicy(),
285
+ ...(packagedPolicy || {}),
286
+ ...(projectPolicy || {})
287
+ };
288
+ }
289
+
290
+ function formatCommand(command, wrapper) {
291
+ if (!wrapper.command) return command;
292
+ return `${wrapper.command} ${command}`;
293
+ }
294
+
295
+ function buildLeanCtxReport(rootPath = process.cwd()) {
296
+ const policy = loadContextPolicy(rootPath);
297
+ const wrapper = detectCommandWrapper();
298
+ const tokenBudget = Number(policy.token_budget || 12000);
299
+ const compactAt = Number(policy.compact_at || 0.7);
300
+ const hardStopAt = Number(policy.hard_stop_at || 0.85);
301
+ const lines = [];
302
+
303
+ lines.push("# LeanCTX Policy");
304
+ lines.push("");
305
+ lines.push(`- Policy: ${policy.name || "leanctx-default"}`);
306
+ lines.push(`- Token budget: ${tokenBudget}`);
307
+ lines.push(`- Compact at: ${Math.round(tokenBudget * compactAt)} tokens (${compactAt})`);
308
+ lines.push(`- Hard stop at: ${Math.round(tokenBudget * hardStopAt)} tokens (${hardStopAt})`);
309
+ lines.push(`- Command wrapper: ${wrapper.command ? `${wrapper.command} (${wrapper.source})` : `none (${wrapper.source})`} - rtk optional`);
310
+ lines.push(`- Wrapper policy: ${policy.wrapper_policy || "rtk optional; keep public commands portable."}`);
311
+ lines.push("");
312
+ lines.push("## Portable Commands");
313
+ for (const command of policy.portable_commands || []) {
314
+ lines.push(`- \`${command}\``);
315
+ }
316
+
317
+ if (wrapper.command) {
318
+ lines.push("");
319
+ lines.push("## Local Wrapper Commands");
320
+ for (const command of policy.portable_commands || []) {
321
+ lines.push(`- \`${formatCommand(command, wrapper)}\``);
322
+ }
323
+ }
324
+
325
+ if (Array.isArray(policy.layers) && policy.layers.length > 0) {
326
+ lines.push("");
327
+ lines.push("## Context Layers");
328
+ for (const layer of policy.layers) {
329
+ lines.push(`- ${layer.name}: ${layer.max_tokens || "unbounded"} tokens`);
330
+ }
331
+ }
332
+
333
+ lines.push("");
334
+ lines.push("Use LeanCTX by loading core state first, then active context, then deferred references only when needed.");
335
+ return lines.join("\n");
336
+ }
337
+
165
338
  function chmodScripts(dir) {
166
339
  if (!fs.existsSync(dir)) return;
167
340
  for (const entry of fs.readdirSync(dir)) {
@@ -185,18 +358,1130 @@ function timestamp() {
185
358
  ].join("");
186
359
  }
187
360
 
361
+ function showStatus() {
362
+ console.log("\x1b[1m\x1b[36m======================================================================\x1b[0m");
363
+ console.log("\x1b[1m\x1b[36m GENESIS HARNESS - STATUS REPORT \x1b[0m");
364
+ console.log("\x1b[1m\x1b[36m======================================================================\x1b[0m");
365
+
366
+ // 1. Current State
367
+ const currentStateFile = path.join(packageRoot, ".codebase", "CURRENT_STATE.md");
368
+ if (fs.existsSync(currentStateFile)) {
369
+ const content = fs.readFileSync(currentStateFile, "utf8");
370
+ console.log("\n\x1b[1m\x1b[32m[+] Repository State (.codebase/CURRENT_STATE.md):\x1b[0m");
371
+ const lines = content.split("\n");
372
+ for (const line of lines) {
373
+ if (line.startsWith("# ") || line.startsWith("## ") || line.startsWith("- ")) {
374
+ console.log(" " + line.trim());
375
+ } else if (line.trim()) {
376
+ console.log(" " + line.trim());
377
+ }
378
+ }
379
+ } else {
380
+ console.log("\n\x1b[1m\x1b[31m[-] Repository State:\x1b[0m .codebase/CURRENT_STATE.md not found.");
381
+ }
382
+
383
+ // 2. Active Planning & Task Tracking
384
+ const stateFile = path.join(packageRoot, ".planning", "STATE.md");
385
+ const roadmapFile = path.join(packageRoot, ".planning", "ROADMAP.md");
386
+ if (fs.existsSync(stateFile) || fs.existsSync(roadmapFile)) {
387
+ console.log("\n\x1b[1m\x1b[32m[+] FSM Active Planning & Task Tracking (.planning/):\x1b[0m");
388
+ if (fs.existsSync(stateFile)) {
389
+ console.log(" \x1b[1m- Current Execution State (.planning/STATE.md):\x1b[0m");
390
+ const content = fs.readFileSync(stateFile, "utf8");
391
+ const lines = content.split("\n");
392
+ for (const line of lines) {
393
+ if (line.includes("Current project state:") || line.includes("Current phase:") || line.includes("Current feature or bug:") || line.includes("Next task:") || line.includes("Latest verification result:")) {
394
+ console.log(` ${line.replace("#", "").trim()}`);
395
+ }
396
+ }
397
+ }
398
+ if (fs.existsSync(roadmapFile)) {
399
+ console.log("\n \x1b[1m- 5-Phase Roadmap Status (.planning/ROADMAP.md):\x1b[0m");
400
+ const content = fs.readFileSync(roadmapFile, "utf8");
401
+ const lines = content.split("\n");
402
+ for (const line of lines) {
403
+ if (line.match(/^-\s*\[[ x~!]\]/)) {
404
+ console.log(` ${line.trim()}`);
405
+ }
406
+ }
407
+ }
408
+ } else {
409
+ console.log("\n\x1b[1m\x1b[33m[-] FSM Active Planning:\x1b[0m No active .planning/ session found. Run `/genesis-init` in Codex to initialize.");
410
+ }
411
+
412
+ // 3. Skills Inventory
413
+ console.log("\n\x1b[1m\x1b[32m[+] Skills Inventory Check (Exactly 25 core skills):\x1b[0m");
414
+ let found = 0;
415
+ let mismatched = 0;
416
+ for (const skillName of skillNames) {
417
+ const skillDir = path.join(sourceRoot, skillName);
418
+ const skillFile = path.join(skillDir, "SKILL.md");
419
+ if (fs.existsSync(skillFile)) {
420
+ found++;
421
+ const content = fs.readFileSync(skillFile, "utf8");
422
+ const nameMatch = content.match(/^name:\s*(.+)$/m);
423
+ const name = nameMatch ? nameMatch[1].trim() : "";
424
+ if (name !== skillName) {
425
+ mismatched++;
426
+ console.log(` \x1b[31m[-] Mismatch:\x1b[0m folder '${skillName}' has frontmatter name '${name}'`);
427
+ }
428
+ } else {
429
+ console.log(` \x1b[31m[-] Missing:\x1b[0m ${skillName} (SKILL.md not found)`);
430
+ }
431
+ }
432
+
433
+ if (found === skillNames.length && mismatched === 0) {
434
+ console.log(` \x1b[32m✓ Success:\x1b[0m All ${skillNames.length} skill folders perfectly standard and synchronized!`);
435
+ } else {
436
+ console.log(` \x1b[31m⚠️ Alert:\x1b[0m Found ${found}/${skillNames.length} skills. Mismatches: ${mismatched}.`);
437
+ }
438
+
439
+ // 4. Verification Test Run Status
440
+ console.log("\n\x1b[1m\x1b[32m[+] Quick Pipeline Commands:\x1b[0m");
441
+ console.log(" - Run structural checks: \x1b[33m./scripts/verify.sh\x1b[0m");
442
+ console.log(" - Run regression tests: \x1b[33m./scripts/run-evals.sh\x1b[0m");
443
+ console.log(" - Run package checks: \x1b[33mnpm run pack:check\x1b[0m");
444
+
445
+ console.log("\n\x1b[1m\x1b[36m======================================================================\x1b[0m\n");
446
+ }
447
+
448
+ function showDocsStatus() {
449
+ console.log("\x1b[1m\x1b[35m======================================================================\x1b[0m");
450
+ console.log("\x1b[1m\x1b[35m GENESIS HARNESS - DOCUMENTATION REPORT \x1b[0m");
451
+ console.log("\x1b[1m\x1b[35m======================================================================\x1b[0m");
452
+
453
+ // 1. Architecture Summary
454
+ const archFile = path.join(packageRoot, ".codebase", "ARCHITECTURE.md");
455
+ if (fs.existsSync(archFile)) {
456
+ console.log("\n\x1b[1m\x1b[32m[+] System Architecture (.codebase/ARCHITECTURE.md):\x1b[0m");
457
+ const content = fs.readFileSync(archFile, "utf8");
458
+ console.log(" " + content.trim().split("\n").filter(l => l.trim() && !l.startsWith("#")).join("\n "));
459
+ }
460
+
461
+ // 2. API Contracts & Specs
462
+ console.log("\n\x1b[1m\x1b[32m[+] API Contracts & Specs (contracts/api/):\x1b[0m");
463
+ const apiDir = path.join(packageRoot, "contracts", "api");
464
+ if (fs.existsSync(apiDir)) {
465
+ const endpoints = fs.readdirSync(apiDir).filter(f => fs.statSync(path.join(apiDir, f)).isDirectory());
466
+ if (endpoints.length > 0) {
467
+ for (const endpoint of endpoints) {
468
+ console.log(` - \x1b[1m${endpoint}\x1b[0m`);
469
+ const files = ["request.json", "response.json", "schema.json", "example.json", "error.json"];
470
+ const found = [];
471
+ for (const file of files) {
472
+ if (fs.existsSync(path.join(apiDir, endpoint, file))) {
473
+ found.push(file.replace(".json", ""));
474
+ }
475
+ }
476
+ console.log(` Files: \x1b[33m${found.join(", ")}\x1b[0m`);
477
+
478
+ // Print request structure preview if request.json exists
479
+ const reqPath = path.join(apiDir, endpoint, "request.json");
480
+ if (fs.existsSync(reqPath)) {
481
+ try {
482
+ const req = JSON.parse(fs.readFileSync(reqPath, "utf8"));
483
+ console.log(` Request: \x1b[90m${JSON.stringify(req).slice(0, 80)}...\x1b[0m`);
484
+ } catch(e) {}
485
+ }
486
+ }
487
+ } else {
488
+ console.log(" No API endpoint contracts defined under contracts/api/.");
489
+ }
490
+ } else {
491
+ console.log(" contracts/api/ directory not found.");
492
+ }
493
+
494
+ // 3. Documentation Density
495
+ console.log("\n\x1b[1m\x1b[32m[+] Documentation Density Stats:\x1b[0m");
496
+ const countMd = (dir) => {
497
+ let count = 0;
498
+ if (!fs.existsSync(dir)) return 0;
499
+ const walk = (d) => {
500
+ for (const f of fs.readdirSync(d)) {
501
+ const full = path.join(d, f);
502
+ if (fs.statSync(full).isDirectory()) {
503
+ walk(full);
504
+ } else if (f.endsWith(".md")) {
505
+ count++;
506
+ }
507
+ }
508
+ };
509
+ walk(dir);
510
+ return count;
511
+ };
512
+
513
+ const codebaseMd = countMd(path.join(packageRoot, ".codebase"));
514
+ const skillsMd = countMd(path.join(packageRoot, ".codex", "skills"));
515
+ console.log(` - Memory documents (.codebase/): \x1b[36m${codebaseMd} Markdown files\x1b[0m`);
516
+ console.log(` - Skill playbooks (.codex/skills/): \x1b[36m${skillsMd} Markdown files\x1b[0m`);
517
+
518
+ // 4. Git Documentation Sync Check
519
+ console.log("\n\x1b[1m\x1b[32m[+] Git Documentation Sync Status:\x1b[0m");
520
+ try {
521
+ const diff = spawnSync("git", ["diff", "--name-only", "HEAD"], { encoding: "utf8" });
522
+ const changed = diff.stdout ? diff.stdout.trim().split("\n").filter(Boolean) : [];
523
+ if (changed.length > 0) {
524
+ const codeChanged = changed.filter(f => !f.match(/^(\.planning\/|docs\/|README|AGENTS\.md)/));
525
+ const docsChanged = changed.filter(f => f.match(/^(\.planning\/|docs\/|README|AGENTS\.md)/));
526
+ if (codeChanged.length > 0 && docsChanged.length === 0) {
527
+ console.log(" \x1b[31m⚠️ Warning:\x1b[0m Code files changed but no documentation/planning files updated.");
528
+ console.log(` Changed code files: \x1b[90m${codeChanged.slice(0, 3).join(", ")}${codeChanged.length > 3 ? "..." : ""}\x1b[0m`);
529
+ } else {
530
+ console.log(" \x1b[32m✓ Synchronized:\x1b[0m Code and documentation changes are perfectly synchronized!");
531
+ }
532
+ } else {
533
+ console.log(" \x1b[32m✓ Clean:\x1b[0m No uncommitted changes. Documentation is 100% in sync.");
534
+ }
535
+ } catch(e) {
536
+ console.log(" Git status check unavailable.");
537
+ }
538
+
539
+ console.log("\n\x1b[1m\x1b[35m======================================================================\x1b[0m\n");
540
+ }
541
+
542
+ const beadsPath = path.join(packageRoot, ".codebase", "beads.json");
543
+
544
+ function getGitCommit() {
545
+ try {
546
+ const git = spawnSync("git", ["rev-parse", "--short", "HEAD"], { encoding: "utf8" });
547
+ if (git.status === 0 && git.stdout) {
548
+ return git.stdout.trim();
549
+ }
550
+ } catch (e) {}
551
+ return "no-git";
552
+ }
553
+
554
+ function readBeads() {
555
+ if (!fs.existsSync(beadsPath)) return [];
556
+ try {
557
+ return JSON.parse(fs.readFileSync(beadsPath, "utf8")) || [];
558
+ } catch (e) {
559
+ return [];
560
+ }
561
+ }
562
+
563
+ function writeBeads(beads) {
564
+ const codebaseDir = path.dirname(beadsPath);
565
+ if (!fs.existsSync(codebaseDir)) {
566
+ fs.mkdirSync(codebaseDir, { recursive: true });
567
+ }
568
+ fs.writeFileSync(beadsPath, JSON.stringify(beads, null, 2), "utf8");
569
+ }
570
+
571
+ function generateUniqueId(beads) {
572
+ const existing = new Set(beads.map(b => b.id));
573
+ while (true) {
574
+ const id = Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0");
575
+ if (!existing.has(id)) return id;
576
+ }
577
+ }
578
+
579
+ function rememberFact(arg1, arg2) {
580
+ let category = "general";
581
+ let fact = "";
582
+
583
+ if (!arg1) {
584
+ console.error("\x1b[31mError: You must provide a fact/insight to remember.\x1b[0m");
585
+ console.log("Usage: genesis-harness remember [category] \"<fact>\"");
586
+ process.exit(1);
587
+ }
588
+
589
+ if (arg2) {
590
+ category = arg1.toLowerCase().trim();
591
+ fact = arg2.trim();
592
+ } else {
593
+ fact = arg1.trim();
594
+ }
595
+
596
+ const beads = readBeads();
597
+ const bead = {
598
+ id: generateUniqueId(beads),
599
+ category,
600
+ fact,
601
+ timestamp: new Date().toISOString(),
602
+ git_commit: getGitCommit()
603
+ };
604
+
605
+ beads.push(bead);
606
+ writeBeads(beads);
607
+
608
+ console.log(`\n\x1b[1m\x1b[32m[+] Remembered (Bead Added):\x1b[0m`);
609
+ console.log(` \x1b[1mID:\x1b[0m \x1b[33m[${bead.id}]\x1b[0m`);
610
+ console.log(` \x1b[1mCategory:\x1b[0m \x1b[36m${bead.category}\x1b[0m`);
611
+ console.log(` \x1b[1mFact:\x1b[0m ${bead.fact}`);
612
+ console.log(` \x1b[1mGit Commit:\x1b[0m \x1b[90m${bead.git_commit}\x1b[0m\n`);
613
+ }
614
+
615
+ function recallFacts(query) {
616
+ const beads = readBeads();
617
+ if (beads.length === 0) {
618
+ console.log("\n\x1b[33m[-] No remembered facts found in database (.codebase/beads.json).\x1b[0m\n");
619
+ return;
620
+ }
621
+
622
+ let filtered = beads;
623
+ if (query) {
624
+ const q = query.toLowerCase().trim();
625
+ filtered = beads.filter(
626
+ b => b.category === q || b.fact.toLowerCase().includes(q) || b.id === q
627
+ );
628
+ }
629
+
630
+ console.log("\x1b[1m\x1b[36m======================================================================\x1b[0m");
631
+ console.log("\x1b[1m\x1b[36m GENESIS HARNESS - RECALLED MEMORIES \x1b[0m");
632
+ console.log("\x1b[1m\x1b[36m======================================================================\x1b[0m");
633
+
634
+ if (filtered.length === 0) {
635
+ console.log(`\n No memories found matching: \x1b[31m"${query}"\x1b[0m\n`);
636
+ } else {
637
+ console.log("");
638
+ for (const bead of filtered) {
639
+ console.log(` • \x1b[1m\x1b[33m[${bead.id}]\x1b[0m [\x1b[36m${bead.category}\x1b[0m] ${bead.fact} \x1b[90m(${bead.git_commit})\x1b[0m`);
640
+ }
641
+ console.log("");
642
+ }
643
+ console.log("\x1b[1m\x1b[36m======================================================================\x1b[0m\n");
644
+ }
645
+
646
+ function forgetFact(id) {
647
+ if (!id) {
648
+ console.error("\x1b[31mError: You must specify a 6-character unique ID to forget.\x1b[0m");
649
+ console.log("Usage: genesis-harness forget <id>");
650
+ process.exit(1);
651
+ }
652
+
653
+ const targetId = id.toLowerCase().trim();
654
+ const beads = readBeads();
655
+ const index = beads.findIndex(b => b.id === targetId);
656
+
657
+ if (index === -1) {
658
+ console.error(`\x1b[31mError: No remembered fact found with ID [${targetId}].\x1b[0m`);
659
+ process.exit(1);
660
+ }
661
+
662
+ const removed = beads.splice(index, 1)[0];
663
+ writeBeads(beads);
664
+
665
+ console.log(`\n\x1b[1m\x1b[32m[+] Forgotten (Bead Deleted):\x1b[0m`);
666
+ console.log(` Successfully removed fact \x1b[33m[${removed.id}]\x1b[0m from category \x1b[36m"${removed.category}"\x1b[0m.\n`);
667
+ }
668
+
669
+ function primeContext() {
670
+ const out = [];
671
+ out.push("# 🤖 AGENT MEMORY PRIMING BOOTSTRAP");
672
+ out.push("");
673
+ out.push("This prompt initializes your active context and project memory to prevent task drift.");
674
+ out.push("");
675
+ out.push("---");
676
+ out.push("");
677
+
678
+ // 1. Coordinates
679
+ out.push("## 📍 1. Session Coordinates");
680
+ out.push(`- **Local Time**: ${new Date().toISOString()}`);
681
+ out.push(`- **Workspace Path**: \`${packageRoot}\``);
682
+ const gitCommit = getGitCommit();
683
+ out.push(`- **Git Commit**: \`${gitCommit}\``);
684
+ out.push("");
685
+
686
+ // 2. Active FSM State
687
+ out.push("## 🔄 2. Active Execution State");
688
+ const stateFile = path.join(packageRoot, ".planning", "STATE.md");
689
+ if (fs.existsSync(stateFile)) {
690
+ const content = fs.readFileSync(stateFile, "utf8");
691
+ const lines = content.split("\n");
692
+ for (const line of lines) {
693
+ if (line.includes("Current project state:") || line.includes("Current phase:") || line.includes("Current feature or bug:") || line.includes("Next task:")) {
694
+ out.push(`- ${line.replace("#", "").trim()}`);
695
+ }
696
+ }
697
+ } else {
698
+ // Fallback to .codebase/state.json
699
+ const stateJsonFile = path.join(packageRoot, ".codebase", "state.json");
700
+ if (fs.existsSync(stateJsonFile)) {
701
+ try {
702
+ const stateObj = JSON.parse(fs.readFileSync(stateJsonFile, "utf8"));
703
+ out.push(`- **Current project state**: \`${stateObj.current_state || "INIT"}\``);
704
+ } catch(e) {}
705
+ } else {
706
+ out.push("- *No active planning session is currently running.*");
707
+ }
708
+ }
709
+ out.push("");
710
+
711
+ // 3. Active Roadmap (Token-Optimized)
712
+ out.push("## 🗺️ 3. Active & Pending Roadmap Tasks");
713
+ const roadmapFile = path.join(packageRoot, ".planning", "ROADMAP.md");
714
+ if (fs.existsSync(roadmapFile)) {
715
+ const content = fs.readFileSync(roadmapFile, "utf8");
716
+ const lines = content.split("\n");
717
+ let taskCount = 0;
718
+ for (const line of lines) {
719
+ if (line.match(/^-\s*\[[ ~!]\]/)) {
720
+ out.push(` ${line.trim()}`);
721
+ taskCount++;
722
+ }
723
+ }
724
+ if (taskCount === 0) {
725
+ out.push(" - *All roadmap tasks are currently marked completed or none are active.*");
726
+ }
727
+ } else {
728
+ out.push("- *Roadmap file (.planning/ROADMAP.md) not found.*");
729
+ }
730
+ out.push("");
731
+
732
+ // 4. Memory Beads / Persistent Insights
733
+ out.push("## 🧬 4. Memory Beads (Stored Core Insights)");
734
+ const beads = readBeads();
735
+ if (beads.length > 0) {
736
+ for (const bead of beads) {
737
+ out.push(`- **[${bead.id}]** [${bead.category}]: ${bead.fact} *(${bead.git_commit})*`);
738
+ }
739
+ } else {
740
+ out.push("- *No persistent facts or insights have been registered yet.*");
741
+ }
742
+ out.push("");
743
+
744
+ // 5. API Contracts Index
745
+ out.push("## 🔌 5. Active API Contracts Map");
746
+ const apiDir = path.join(packageRoot, "contracts", "api");
747
+ if (fs.existsSync(apiDir)) {
748
+ const endpoints = fs.readdirSync(apiDir).filter(f => fs.statSync(path.join(apiDir, f)).isDirectory());
749
+ if (endpoints.length > 0) {
750
+ for (const endpoint of endpoints) {
751
+ out.push(`- Endpoint: \`/api/${endpoint}\``);
752
+ }
753
+ } else {
754
+ out.push("- *No active API endpoints registered.*");
755
+ }
756
+ } else {
757
+ out.push("- *No contracts/api directory found.*");
758
+ }
759
+ out.push("");
760
+
761
+ // 6. Zero-Drift Playbook Rules
762
+ out.push("## 🛡️ 6. Zero-Drift Playbook Rules");
763
+ out.push("Always adhere strictly to these operational constraints:");
764
+ out.push("1. **Verify first**: Run `./scripts/verify.sh` to check FSM validation and structure.");
765
+ out.push("2. **Update memory**: Ensure `.codebase/CURRENT_STATE.md` is updated at the end of every turn.");
766
+ out.push("3. **Single source**: Avoid duplicating plans across multi-line markdown logs; use `genesis-harness remember` to store critical project coordinates.");
767
+ out.push("4. **TDD Pattern**: Create or update failing tests in `tests/` before making changes to public behaviors.");
768
+ out.push("");
769
+ out.push("## 🪶 7. LeanCTX Policy");
770
+ out.push(buildLeanCtxReport(process.cwd()));
771
+ out.push("");
772
+ out.push("---");
773
+ out.push("");
774
+
775
+ console.log(out.join("\n"));
776
+ }
777
+
778
+ function showLeanCtx() {
779
+ console.log(buildLeanCtxReport(process.cwd()));
780
+ }
781
+
782
+ function openFileNatively(filePath) {
783
+ if (process.platform === "win32") {
784
+ const cp = spawnSync("cmd.exe", ["/c", "start", "", filePath], { shell: true });
785
+ return cp.status === 0;
786
+ }
787
+ let cmd = "open";
788
+ if (process.platform === "linux") {
789
+ cmd = "xdg-open";
790
+ }
791
+
792
+ const cp = spawnSync(cmd, [filePath]);
793
+ return cp.status === 0;
794
+ }
795
+
796
+ function discoverMockups(rootPath = packageRoot) {
797
+ const mockups = [];
798
+ const featuresDir = path.join(rootPath, ".planning", "features");
799
+ const bugsDir = path.join(rootPath, ".planning", "bugs");
800
+
801
+ const scanDir = (dir, type) => {
802
+ if (!fs.existsSync(dir)) return;
803
+ const entries = fs.readdirSync(dir);
804
+ for (const entry of entries) {
805
+ const fullPath = path.join(dir, entry);
806
+ if (fs.statSync(fullPath).isDirectory()) {
807
+ const files = fs.readdirSync(fullPath);
808
+ for (const file of files) {
809
+ const ext = path.extname(file).toLowerCase();
810
+ if ([".png", ".jpg", ".jpeg", ".webp"].includes(ext)) {
811
+ mockups.push({
812
+ id: `${type}-${entry}-${file}`,
813
+ title: `${type === "feature" ? "[Feature]" : "[Bug]"} ${entry}`,
814
+ folder: entry,
815
+ fileName: file,
816
+ fullPath: path.join(fullPath, file)
817
+ });
818
+ }
819
+ }
820
+ }
821
+ }
822
+ };
823
+
824
+ scanDir(featuresDir, "feature");
825
+ scanDir(bugsDir, "bug");
826
+ return mockups;
827
+ }
828
+
829
+ function viewMockupsInteractive(arg) {
830
+ if (arg) {
831
+ if (fs.existsSync(arg) && fs.statSync(arg).isFile()) {
832
+ console.log(`\n\x1b[1m\x1b[32m[+] Opening direct file:\x1b[0m ${arg}`);
833
+ openFileNatively(arg);
834
+ return;
835
+ }
836
+
837
+ const mockups = discoverMockups();
838
+ const found = mockups.find(m => m.folder === arg || m.id === arg);
839
+ if (found) {
840
+ console.log(`\n\x1b[1m\x1b[32m[+] Opening mockup for ${found.title}:\x1b[0m ${found.fileName}`);
841
+ openFileNatively(found.fullPath);
842
+ return;
843
+ }
844
+
845
+ console.error(`\x1b[31mError: No mockup found matching direct path or slug "${arg}".\x1b[0m`);
846
+ process.exit(1);
847
+ }
848
+
849
+ const mockups = discoverMockups();
850
+ if (mockups.length === 0) {
851
+ console.log("\n\x1b[33m[-] No mockup images (.png, .jpg, .webp) found under .planning/features/ or .planning/bugs/.\x1b[0m\n");
852
+ return;
853
+ }
854
+
855
+ let selectedIndex = 0;
856
+ let currentView = "LIST"; // "LIST" or "DETAIL"
857
+
858
+ const renderMenu = () => {
859
+ console.clear();
860
+ console.log("\x1b[1m\x1b[36m======================================================================\x1b[0m");
861
+ console.log("\x1b[1m\x1b[36m GENESIS HARNESS - MOCKUP GALLERY VIEWER \x1b[0m");
862
+ console.log("\x1b[1m\x1b[36m======================================================================\x1b[0m\n");
863
+
864
+ if (currentView === "LIST") {
865
+ console.log(" \x1b[1mUse Up/Down Arrow to navigate, Right Arrow (or Enter) to view.\x1b[0m");
866
+ console.log(" \x1b[90mPress Esc or Ctrl+C to exit.\x1b[0m\n");
867
+ console.log(" \x1b[1mDISCOVERED SCREENS / MOCKUPS:\x1b[0m");
868
+ console.log(" ------------------------------------------------------------------");
869
+
870
+ mockups.forEach((mockup, idx) => {
871
+ if (idx === selectedIndex) {
872
+ console.log(` \x1b[1m\x1b[36m➔ ${mockup.title} (${mockup.fileName})\x1b[0m`);
873
+ } else {
874
+ console.log(` \x1b[90m${mockup.title} (${mockup.fileName})\x1b[0m`);
875
+ }
876
+ });
877
+ console.log(" ------------------------------------------------------------------\n");
878
+ } else if (currentView === "DETAIL") {
879
+ const selected = mockups[selectedIndex];
880
+ console.log(" \x1b[1m\x1b[32m[+] LAUNCHED SYSTEM VIEW FOR:\x1b[0m \x1b[1m" + selected.title + "\x1b[0m\n");
881
+ console.log(` - \x1b[1mMockup File:\x1b[0m ${selected.fileName}`);
882
+ console.log(` - \x1b[1mFolder Path:\x1b[0m ${path.dirname(selected.fullPath)}`);
883
+
884
+ let sizeText = "Unknown";
885
+ try {
886
+ const stats = fs.statSync(selected.fullPath);
887
+ sizeText = `${(stats.size / 1024).toFixed(1)} KB`;
888
+ } catch (e) {}
889
+ console.log(` - \x1b[1mFile Size:\x1b[0m ${sizeText}`);
890
+ console.log("");
891
+ console.log(" ==================================================================");
892
+ console.log(" \x1b[36m[OS SYSTEM PREVIEW LAUNCHED]\x1b[0m");
893
+ console.log(" The mockup has been opened in your system's native image viewer.");
894
+ console.log(" ==================================================================\n");
895
+ console.log(" \x1b[1m\x1b[33m← Press Left Arrow to go BACK to list.\x1b[0m");
896
+ console.log(" \x1b[90mPress Esc or Ctrl+C to exit.\x1b[0m\n");
897
+ }
898
+ console.log("\x1b[1m\x1b[36m======================================================================\x1b[0m");
899
+ };
900
+
901
+ process.stdin.setRawMode(true);
902
+ process.stdin.resume();
903
+ process.stdin.setEncoding("utf8");
904
+
905
+ const cleanExit = () => {
906
+ process.stdin.setRawMode(false);
907
+ process.stdin.pause();
908
+ console.clear();
909
+ console.log("\n\x1b[32m[+] Exited Mockup Gallery Viewer.\x1b[0m\n");
910
+ process.exit(0);
911
+ };
912
+
913
+ renderMenu();
914
+
915
+ process.stdin.on("data", (key) => {
916
+ if (key === "\u0003" || key === "\u001b") {
917
+ cleanExit();
918
+ }
919
+
920
+ if (currentView === "LIST") {
921
+ if (key === "\u001b[A") {
922
+ selectedIndex = (selectedIndex - 1 + mockups.length) % mockups.length;
923
+ renderMenu();
924
+ }
925
+ else if (key === "\u001b[B") {
926
+ selectedIndex = (selectedIndex + 1) % mockups.length;
927
+ renderMenu();
928
+ }
929
+ else if (key === "\u001b[C" || key === "\r") {
930
+ currentView = "DETAIL";
931
+ const selected = mockups[selectedIndex];
932
+ openFileNatively(selected.fullPath);
933
+ renderMenu();
934
+ }
935
+ } else if (currentView === "DETAIL") {
936
+ if (key === "\u001b[D") {
937
+ currentView = "LIST";
938
+ renderMenu();
939
+ }
940
+ }
941
+ });
942
+ }
943
+
944
+ function syncContext() {
945
+ const srcDirs = ['src', 'lib', 'tests', 'bin'];
946
+ const codebaseDir = path.join(process.cwd(), '.codebase');
947
+ const contextFile = path.join(codebaseDir, 'COMPRESSED_CONTEXT.md');
948
+ const visualFile = path.join(codebaseDir, 'VISUAL_GRAPH.md');
949
+
950
+ if (!fs.existsSync(codebaseDir)) {
951
+ fs.mkdirSync(codebaseDir, { recursive: true });
952
+ }
953
+
954
+ let output = '# Compressed Context & Dependency Graph\n\n';
955
+ let visualOutput = '# Visual Project Graph\n\n';
956
+ const depEdges = [];
957
+
958
+ let parser, traverse;
959
+ try {
960
+ parser = require('@babel/parser');
961
+ traverse = require('@babel/traverse').default;
962
+ } catch (e) {
963
+ console.error('[genesis-harness] AST parser dependencies missing. Run: npm install');
964
+ process.exit(1);
965
+ }
966
+
967
+ function walk(dir) {
968
+ if (!fs.existsSync(dir)) return;
969
+ const files = fs.readdirSync(dir);
970
+ for (const file of files) {
971
+ const fullPath = path.join(dir, file);
972
+ const stat = fs.statSync(fullPath);
973
+ if (stat.isDirectory() && file !== 'node_modules') {
974
+ walk(fullPath);
975
+ } else if (file.endsWith('.js') || file.endsWith('.ts')) {
976
+ const content = fs.readFileSync(fullPath, 'utf8');
977
+ const exportsList = [];
978
+ const importsList = [];
979
+ const relativePath = fullPath.replace(process.cwd() + '/', '');
980
+ const featuresList = [];
981
+
982
+ try {
983
+ const ast = parser.parse(content, {
984
+ sourceType: 'module',
985
+ plugins: ['typescript', 'jsx']
986
+ });
987
+
988
+ if (ast.comments) {
989
+ ast.comments.forEach(comment => {
990
+ const match = comment.value.match(/@feature:\s*(.+)/i);
991
+ if (match) {
992
+ featuresList.push(match[1].trim());
993
+ }
994
+ });
995
+ }
996
+
997
+ traverse(ast, {
998
+ ExportNamedDeclaration(path) {
999
+ const decl = path.node.declaration;
1000
+ if (decl) {
1001
+ if (decl.type === 'ClassDeclaration' && decl.id) {
1002
+ exportsList.push('class ' + decl.id.name);
1003
+ } else if (decl.type === 'FunctionDeclaration' && decl.id) {
1004
+ exportsList.push('function ' + decl.id.name);
1005
+ } else if (decl.type === 'VariableDeclaration') {
1006
+ decl.declarations.forEach(d => {
1007
+ if (d.id) exportsList.push('const ' + d.id.name);
1008
+ });
1009
+ }
1010
+ }
1011
+ },
1012
+ ImportDeclaration(path) {
1013
+ importsList.push(path.node.source.value);
1014
+ depEdges.push(` "${relativePath}" --> "${path.node.source.value}"`);
1015
+ },
1016
+ CallExpression(path) {
1017
+ if (path.node.callee.name === 'require' && path.node.arguments.length > 0) {
1018
+ if (path.node.arguments[0].type === 'StringLiteral') {
1019
+ importsList.push(path.node.arguments[0].value);
1020
+ depEdges.push(` "${relativePath}" --> "${path.node.arguments[0].value}"`);
1021
+ }
1022
+ }
1023
+ }
1024
+ });
1025
+ } catch (err) {
1026
+ exportsList.push('// AST Parse Error: ' + err.message);
1027
+ }
1028
+
1029
+ if (exportsList.length > 0 || importsList.length > 0 || featuresList.length > 0) {
1030
+ output += '## ' + relativePath + '\n';
1031
+ if (featuresList.length > 0) {
1032
+ output += '### Implements Features\n';
1033
+ featuresList.forEach(f => output += '- `' + f + '`\n');
1034
+ }
1035
+ if (exportsList.length > 0) {
1036
+ output += '### Exports\n';
1037
+ exportsList.forEach(sig => output += '- `' + sig + '`\n');
1038
+ }
1039
+ if (importsList.length > 0) {
1040
+ output += '### Dependencies\n';
1041
+ importsList.forEach(imp => output += '- `' + imp + '`\n');
1042
+ }
1043
+ output += '\n';
1044
+ }
1045
+ }
1046
+ }
1047
+ }
1048
+
1049
+ srcDirs.forEach(dir => walk(path.join(process.cwd(), dir)));
1050
+ // Generate Visual Graph
1051
+ visualOutput += '## Harness Relationship Map\n\n```mermaid\nflowchart LR\n';
1052
+ visualOutput += ' manifest[".codex-plugin/plugin.json"] --> skills[".codex/skills/*"]\n';
1053
+ visualOutput += ' package["package.json"] --> cli["bin/genesis-harness.js"]\n';
1054
+ visualOutput += ' package --> verify["scripts/verify.sh"]\n';
1055
+ visualOutput += ' package --> evals["scripts/run-evals.sh"]\n';
1056
+ visualOutput += ' cli --> install["install / postinstall"]\n';
1057
+ visualOutput += ' cli --> hooks["setup-hooks"]\n';
1058
+ visualOutput += ' hooks --> docsgate["genesis-harness docs-gate"]\n';
1059
+ visualOutput += ' docsgate --> docsync["check-docs-sync.sh"]\n';
1060
+ visualOutput += ' docsgate --> specsync["check-spec-changelog.sh"]\n';
1061
+ visualOutput += ' skills --> contracts["contracts/"]\n';
1062
+ visualOutput += ' skills --> fixtures["fixtures/"]\n';
1063
+ visualOutput += ' skills --> tests["tests/ + playwright/"]\n';
1064
+ visualOutput += ' skills --> memory[".codebase/"]\n';
1065
+ visualOutput += ' verify --> skills\n';
1066
+ visualOutput += ' verify --> contracts\n';
1067
+ visualOutput += ' verify --> fixtures\n';
1068
+ visualOutput += ' verify --> memory\n';
1069
+ visualOutput += ' evals --> install\n';
1070
+ visualOutput += ' evals --> cli\n';
1071
+ visualOutput += ' evals --> unit["tests/unit/*.test.js"]\n';
1072
+ visualOutput += ' evals --> integration["tests/integration/*.test.js"]\n';
1073
+ visualOutput += ' evals --> pack["npm pack smoke"]\n';
1074
+ visualOutput += '```\n\n';
1075
+
1076
+ visualOutput += '## Skill Workflow Relationships\n\n```mermaid\nflowchart TD\n';
1077
+ visualOutput += ' harness["genesis-harness"] --> planning["genesis-planning"]\n';
1078
+ visualOutput += ' harness --> research["genesis-research-first"]\n';
1079
+ visualOutput += ' planning --> architecture["genesis-architecture"]\n';
1080
+ visualOutput += ' planning --> api["genesis-api-contract"]\n';
1081
+ visualOutput += ' planning --> design["genesis-design-spec"]\n';
1082
+ visualOutput += ' api --> apisync["genesis-api-sync"]\n';
1083
+ visualOutput += ' design --> ui["genesis-ui-ux-test"]\n';
1084
+ visualOutput += ' api --> specimpact["spec-impact-engine"]\n';
1085
+ visualOutput += ' specimpact --> specprop["genesis-spec-propagation"]\n';
1086
+ visualOutput += ' specprop --> docs["genesis-docs-automation"]\n';
1087
+ visualOutput += ' ui --> verifybefore["genesis-verification-before-completion"]\n';
1088
+ visualOutput += ' apisync --> verifybefore\n';
1089
+ visualOutput += ' docs --> verifybefore\n';
1090
+ visualOutput += ' verifybefore --> release["genesis-release"]\n';
1091
+ visualOutput += ' harness --> memorymap["genesis-codebase-map"]\n';
1092
+ visualOutput += ' harness --> observability["genesis-observability-automation"]\n';
1093
+ visualOutput += '```\n\n';
1094
+
1095
+ visualOutput += '## Code Dependency Hints\n\n```mermaid\nflowchart TD\n';
1096
+ if (depEdges.length > 0) {
1097
+ visualOutput += depEdges.join('\n') + '\n';
1098
+ } else {
1099
+ visualOutput += ' Root["No dependencies found"]\n';
1100
+ }
1101
+ visualOutput += '```\n\n';
1102
+
1103
+ // Parse Roadmap for features and roles
1104
+ const roadmapFile = path.join(process.cwd(), '.planning', 'ROADMAP.md');
1105
+ if (fs.existsSync(roadmapFile)) {
1106
+ visualOutput += '## .planning/ROADMAP.md Derived Feature Status\n\n```mermaid\ngraph TD\n';
1107
+ visualOutput += ' classDef completed fill:#d4edda,stroke:#28a745,stroke-width:2px;\n';
1108
+ visualOutput += ' classDef inprogress fill:#fff3cd,stroke:#ffc107,stroke-width:2px;\n';
1109
+ visualOutput += ' classDef pending fill:#e2e3e5,stroke:#6c757d,stroke-width:2px;\n';
1110
+
1111
+ const rmContent = fs.readFileSync(roadmapFile, 'utf8').split('\n');
1112
+ const roles = [];
1113
+ let currentRoleObj = { title: 'General', tasks: [] };
1114
+
1115
+ let taskIdCounter = 0;
1116
+ const allTasksMap = new Map();
1117
+
1118
+ rmContent.forEach(line => {
1119
+ if (line.match(/^#+\s+(.+)/)) {
1120
+ const title = line.match(/^#+\s+(.+)/)[1].trim();
1121
+ // If switching roles, push the current one if it has tasks
1122
+ if (currentRoleObj.tasks.length > 0) {
1123
+ roles.push(currentRoleObj);
1124
+ }
1125
+ currentRoleObj = { title: title, tasks: [] };
1126
+ } else if (line.match(/^-\s*\[([ xX~!\/])\]\s+(.+)/)) {
1127
+ const match = line.match(/^-\s*\[([ xX~!\/])\]\s+(.+)/);
1128
+ const statusChar = match[1].toLowerCase();
1129
+ let rawName = match[2].trim();
1130
+ let dependsOn = [];
1131
+ let mappedFiles = [];
1132
+
1133
+ const depMatch = rawName.match(/\(depends_on:\s*(.+?)\)/i);
1134
+ if (depMatch) {
1135
+ dependsOn = depMatch[1].split(',').map(s => s.trim());
1136
+ rawName = rawName.replace(/\(depends_on:\s*.+?\)/i, '').trim();
1137
+ }
1138
+
1139
+ const filesMatch = rawName.match(/\(files:\s*(.+?)\)/i);
1140
+ if (filesMatch) {
1141
+ mappedFiles = filesMatch[1].split(',').map(s => s.trim());
1142
+ rawName = rawName.replace(/\(files:\s*.+?\)/i, '').trim();
1143
+ }
1144
+
1145
+ const taskId = `Task${taskIdCounter++}`;
1146
+ allTasksMap.set(rawName.toLowerCase(), taskId);
1147
+
1148
+ currentRoleObj.tasks.push({
1149
+ id: taskId,
1150
+ statusChar: statusChar,
1151
+ name: rawName,
1152
+ dependsOn: dependsOn,
1153
+ mappedFiles: mappedFiles
1154
+ });
1155
+ }
1156
+ });
1157
+ if (currentRoleObj.tasks.length > 0) {
1158
+ roles.push(currentRoleObj);
1159
+ }
1160
+
1161
+ if (roles.length === 0) {
1162
+ visualOutput += ' Project["Project Roadmap"] --> NoTasks["No tasks found"]\n';
1163
+ } else {
1164
+ roles.forEach((r, idx) => {
1165
+ visualOutput += ` subgraph Role_${idx} ["${r.title}"]\n`;
1166
+ r.tasks.forEach(t => {
1167
+ let label = `Roadmap task ${t.id.replace('Task', '')}`;
1168
+ visualOutput += ` ${t.id}["${label}"]\n`;
1169
+ if (t.statusChar === 'x') {
1170
+ visualOutput += ` class ${t.id} completed;\n`;
1171
+ } else if (t.statusChar === '/' || t.statusChar === '~' || t.statusChar === '!') {
1172
+ visualOutput += ` class ${t.id} inprogress;\n`;
1173
+ } else {
1174
+ visualOutput += ` class ${t.id} pending;\n`;
1175
+ }
1176
+ });
1177
+ visualOutput += ` end\n`;
1178
+ });
1179
+
1180
+ // Draw dependencies
1181
+ roles.forEach(r => {
1182
+ r.tasks.forEach(t => {
1183
+ if (t.dependsOn.length > 0) {
1184
+ t.dependsOn.forEach(depName => {
1185
+ const depId = allTasksMap.get(depName.toLowerCase());
1186
+ if (depId) {
1187
+ visualOutput += ` ${depId} --> ${t.id}\n`;
1188
+ }
1189
+ });
1190
+ }
1191
+ });
1192
+ });
1193
+ }
1194
+ visualOutput += '```\n\n';
1195
+
1196
+ // Đưa Roadmap vào COMPRESSED_CONTEXT.md cho AI đọc (Dạng text thuần)
1197
+ output += '\n## Project Planning & Roadmap\n';
1198
+ output += rmContent.join('\n') + '\n';
1199
+ }
1200
+
1201
+ fs.writeFileSync(contextFile, output);
1202
+ fs.writeFileSync(visualFile, visualOutput);
1203
+ console.log('[genesis-harness] Context compressed and saved to ' + contextFile);
1204
+ console.log('[genesis-harness] Visual Graph saved to ' + visualFile);
1205
+ }
1206
+
1207
+ function setupHooks(rootPath = process.cwd()) {
1208
+ if (!rootPath) {
1209
+ console.log('[genesis-harness] Project root not detected, skipping hooks setup.');
1210
+ return;
1211
+ }
1212
+
1213
+ const hooksDir = path.join(rootPath, '.git', 'hooks');
1214
+ const preCommitFile = path.join(hooksDir, 'pre-commit');
1215
+
1216
+ if (!fs.existsSync(hooksDir)) {
1217
+ console.log('[genesis-harness] Not a git repository, skipping hooks setup.');
1218
+ return;
1219
+ }
1220
+
1221
+ const hookContent = `#!/bin/sh
1222
+ # genesis-harness auto-sync
1223
+ echo "[genesis-harness] Syncing compressed context before commit..."
1224
+ npx genesis-harness sync
1225
+ echo "[genesis-harness] Running docs drift gate..."
1226
+ npx genesis-harness docs-gate
1227
+ git add .codebase/COMPRESSED_CONTEXT.md .codebase/VISUAL_GRAPH.md 2>/dev/null || true
1228
+ `;
1229
+
1230
+ if (fs.existsSync(preCommitFile)) {
1231
+ const existingContent = fs.readFileSync(preCommitFile, 'utf8');
1232
+ if (existingContent !== hookContent) {
1233
+ const backupPath = preCommitFile + '.backup.' + Date.now();
1234
+ fs.renameSync(preCommitFile, backupPath);
1235
+ console.log('[genesis-harness] Existing pre-commit hook backed up to ' + backupPath);
1236
+ } else {
1237
+ console.log('[genesis-harness] Git hooks already up to date.');
1238
+ return;
1239
+ }
1240
+ }
1241
+
1242
+ fs.writeFileSync(preCommitFile, hookContent);
1243
+ fs.chmodSync(preCommitFile, '755');
1244
+ console.log('[genesis-harness] Git pre-commit hook installed successfully.');
1245
+ }
1246
+
1247
+ function runDocsGate() {
1248
+ const docsSyncScript = path.join(packageRoot, ".codex", "skills", "genesis-harness", "scripts", "check-docs-sync.sh");
1249
+ const specChangelogScript = path.join(packageRoot, ".codex", "skills", "genesis-harness", "scripts", "check-spec-changelog.sh");
1250
+ const bash = resolveBash();
1251
+
1252
+ if (!fs.existsSync(docsSyncScript)) fail(`missing docs sync gate at ${docsSyncScript}`);
1253
+
1254
+ const docsResult = spawnSync(bash, [docsSyncScript, process.cwd()], {
1255
+ stdio: "inherit",
1256
+ env: process.env
1257
+ });
1258
+ if (docsResult.status) process.exit(docsResult.status);
1259
+
1260
+ if (fs.existsSync(path.join(process.cwd(), ".planning", "SPEC_CHANGELOG.md")) && fs.existsSync(specChangelogScript)) {
1261
+ const specResult = spawnSync(bash, [specChangelogScript, process.cwd()], {
1262
+ stdio: "inherit",
1263
+ env: process.env
1264
+ });
1265
+ if (specResult.status) process.exit(specResult.status);
1266
+ }
1267
+ }
1268
+
1269
+ /**
1270
+ * runVerifyGate() — L09 Victory Blocker
1271
+ *
1272
+ * Runs ALL required verification gates in sequence.
1273
+ * Agent MUST call this before claiming any task is done.
1274
+ * Exits with non-zero if any gate fails — prevents "under-finish" hallucination.
1275
+ */
1276
+ function runVerifyGate() {
1277
+ const bash = resolveBash();
1278
+ const verifyScript = path.join(packageRoot, "scripts", "verify.sh");
1279
+ const evalsScript = path.join(packageRoot, "scripts", "run-evals.sh");
1280
+ const coldStartScript = path.join(packageRoot, "scripts", "cold-start-check.js");
1281
+
1282
+ console.log("\x1b[1m\x1b[36m══════════════════════════════════════════════════════\x1b[0m");
1283
+ console.log("\x1b[1m\x1b[36m GENESIS HARNESS — VERIFY-GATE (L09 Victory Blocker) \x1b[0m");
1284
+ console.log("\x1b[1m\x1b[36m══════════════════════════════════════════════════════\x1b[0m");
1285
+ console.log("\x1b[33mRunning all verification gates. Task is NOT done until all pass.\x1b[0m\n");
1286
+
1287
+ const gates = [
1288
+ {
1289
+ name: "1. Structural verify (verify.sh)",
1290
+ run: () => spawnSync(bash, [verifyScript], { stdio: "inherit", env: process.env }).status
1291
+ },
1292
+ {
1293
+ name: "2. Feature registry + observability (feature_registry.test.js)",
1294
+ run: () => spawnSync(process.execPath, [
1295
+ path.join(packageRoot, "tests", "unit", "feature_registry.test.js")
1296
+ ], { stdio: "inherit", env: process.env }).status
1297
+ },
1298
+ {
1299
+ name: "3. Cold-start check (cold-start-check.js)",
1300
+ run: () => fs.existsSync(coldStartScript)
1301
+ ? spawnSync(process.execPath, [coldStartScript], { stdio: "inherit", env: process.env }).status
1302
+ : 0
1303
+ },
1304
+ {
1305
+ name: "4. Unit tests (tests/unit/*.test.js)",
1306
+ run: () => {
1307
+ const unitDir = path.join(packageRoot, "tests", "unit");
1308
+ if (!fs.existsSync(unitDir)) return 0;
1309
+ for (const f of fs.readdirSync(unitDir).filter(f => f.endsWith(".test.js"))) {
1310
+ const result = spawnSync(process.execPath, [path.join(unitDir, f)], {
1311
+ stdio: "inherit", env: process.env
1312
+ });
1313
+ if (result.status) return result.status;
1314
+ }
1315
+ return 0;
1316
+ }
1317
+ }
1318
+ ];
1319
+
1320
+ let allPassed = true;
1321
+ for (const gate of gates) {
1322
+ process.stdout.write(`\n\x1b[33m▶ ${gate.name}\x1b[0m\n`);
1323
+ const code = gate.run();
1324
+ if (code !== 0) {
1325
+ console.log(`\x1b[31m✗ FAILED (exit ${code})\x1b[0m`);
1326
+ allPassed = false;
1327
+ break; // Stop on first failure
1328
+ }
1329
+ console.log(`\x1b[32m✓ PASSED\x1b[0m`);
1330
+ }
1331
+
1332
+ console.log("\n\x1b[1m\x1b[36m══════════════════════════════════════════════════════\x1b[0m");
1333
+ if (allPassed) {
1334
+ console.log("\x1b[1m\x1b[32m✓ ALL GATES PASSED — Task may now be declared DONE.\x1b[0m");
1335
+ console.log("\x1b[32mUpdate .codebase/CURRENT_STATE.md and RECOVERY_POINTS.md.\x1b[0m");
1336
+ } else {
1337
+ console.log("\x1b[1m\x1b[31m✗ VERIFICATION FAILED — Do NOT declare this task done.\x1b[0m");
1338
+ console.log("\x1b[31mFix the failing gate, then re-run: genesis-harness verify-gate\x1b[0m");
1339
+ process.exit(1);
1340
+ }
1341
+ console.log("\x1b[1m\x1b[36m══════════════════════════════════════════════════════\x1b[0m\n");
1342
+ }
1343
+
1344
+ function runColdStart() {
1345
+ const coldStartScript = path.join(packageRoot, "scripts", "cold-start-check.js");
1346
+ if (fs.existsSync(coldStartScript)) {
1347
+ const result = spawnSync(process.execPath, [coldStartScript], { stdio: "inherit", env: process.env });
1348
+ process.exit(result.status);
1349
+ } else {
1350
+ console.error("Cold start script not found.");
1351
+ process.exit(1);
1352
+ }
1353
+ }
1354
+
1355
+ function healTest(testCommand) {
1356
+ if (!testCommand) {
1357
+ console.error('[genesis-harness] Missing test command. Usage: genesis-harness heal "npm test"');
1358
+ process.exit(1);
1359
+ }
1360
+
1361
+ const { execSync } = require('child_process');
1362
+ try {
1363
+ console.log(`[genesis-harness] Running test: ${testCommand}`);
1364
+ execSync(testCommand, { stdio: 'inherit' });
1365
+ console.log('[genesis-harness] Tests passed! No healing required.');
1366
+ } catch (error) {
1367
+ const errorLog = error.stdout ? error.stdout.toString() : error.message;
1368
+ const planningDir = path.join(process.cwd(), '.planning');
1369
+ if (!fs.existsSync(planningDir)) {
1370
+ fs.mkdirSync(planningDir, { recursive: true });
1371
+ }
1372
+ const logFile = path.join(planningDir, 'LAST_ERROR.log');
1373
+ fs.writeFileSync(logFile, errorLog);
1374
+
1375
+ console.log('\\n\\x1b[31m\\x1b[1m[AGENT_DIRECTIVE] TESTS FAILED. READ .planning/LAST_ERROR.log AND IMMEDIATELY EXECUTE genesis-debug-guide TO FIX THE CODE. DO NOT STOP UNTIL TESTS PASS.\\x1b[0m\\n');
1376
+ process.exit(1);
1377
+ }
1378
+ }
1379
+
1380
+ function mcpSetupInteractive() {
1381
+ const options = [
1382
+ { name: "@modelcontextprotocol/server-puppeteer", desc: "Browser UI Testing", selected: true },
1383
+ { name: "@modelcontextprotocol/server-fetch", desc: "URL Markdown Reader", selected: true },
1384
+ { name: "@modelcontextprotocol/server-github", desc: "Repo & PR management", selected: false },
1385
+ { name: "@modelcontextprotocol/server-memory", desc: "Knowledge Graph Memory", selected: true },
1386
+ { name: "@modelcontextprotocol/server-sqlite", desc: "Vector Memory DB", selected: false }
1387
+ ];
1388
+
1389
+ let selectedIndex = 0;
1390
+
1391
+ const renderMenu = () => {
1392
+ console.clear();
1393
+ console.log("\x1b[1m\x1b[36m======================================================================\x1b[0m");
1394
+ console.log("\x1b[1m\x1b[36m GENESIS HARNESS - MCP INSTALLER \x1b[0m");
1395
+ console.log("\x1b[1m\x1b[36m======================================================================\x1b[0m\n");
1396
+ console.log(" \x1b[1mSelect which MCP Servers you want to install globally.\x1b[0m");
1397
+ console.log(" Use \x1b[33mUp/Down Arrow\x1b[0m to navigate.");
1398
+ console.log(" Use \x1b[33mSpace\x1b[0m to toggle selection.");
1399
+ console.log(" Press \x1b[32mEnter\x1b[0m to confirm and install.");
1400
+ console.log(" Press \x1b[90mEsc or Ctrl+C\x1b[0m to cancel.\n");
1401
+
1402
+ options.forEach((opt, idx) => {
1403
+ const checkbox = opt.selected ? "\x1b[32m[x]\x1b[0m" : "[ ]";
1404
+ const cursor = idx === selectedIndex ? "\x1b[1m\x1b[36m➔\x1b[0m " : " ";
1405
+ const name = idx === selectedIndex ? `\x1b[1m${opt.name}\x1b[0m` : opt.name;
1406
+ console.log(` ${cursor} ${checkbox} ${name.padEnd(50)} \x1b[90m(${opt.desc})\x1b[0m`);
1407
+ });
1408
+ console.log("\n\x1b[1m\x1b[36m======================================================================\x1b[0m");
1409
+ };
1410
+
1411
+ process.stdin.setRawMode(true);
1412
+ process.stdin.resume();
1413
+ process.stdin.setEncoding("utf8");
1414
+
1415
+ const cleanExit = () => {
1416
+ process.stdin.setRawMode(false);
1417
+ process.stdin.pause();
1418
+ console.clear();
1419
+ console.log("\n\x1b[33m[-] MCP Setup Cancelled.\x1b[0m\n");
1420
+ process.exit(0);
1421
+ };
1422
+
1423
+ const executeInstall = () => {
1424
+ process.stdin.setRawMode(false);
1425
+ process.stdin.pause();
1426
+ console.clear();
1427
+ const toInstall = options.filter(o => o.selected).map(o => o.name);
1428
+
1429
+ if (toInstall.length === 0) {
1430
+ console.log("\n\x1b[33m[-] No MCP servers selected. Exiting.\x1b[0m\n");
1431
+ process.exit(0);
1432
+ }
1433
+
1434
+ console.log(`\n\x1b[1m\x1b[32m[+] Installing selected MCP servers globally...\x1b[0m\n`);
1435
+ const args = ["install", "-g", ...toInstall];
1436
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
1437
+
1438
+ const result = spawnSync(npmCmd, args, { stdio: "inherit", env: process.env });
1439
+ if (result.status === 0) {
1440
+ console.log(`\n\x1b[1m\x1b[32m✓ Successfully installed MCP servers.\x1b[0m`);
1441
+ console.log(`You can now configure your Agent Client to use them. See mcp.example.json.\n`);
1442
+ } else {
1443
+ console.error(`\n\x1b[1m\x1b[31m[-] Installation failed with status ${result.status}\x1b[0m\n`);
1444
+ }
1445
+ process.exit(result.status || 0);
1446
+ };
1447
+
1448
+ renderMenu();
1449
+
1450
+ process.stdin.on("data", (key) => {
1451
+ if (key === "\u0003" || key === "\u001b") { // Ctrl+C or Esc
1452
+ cleanExit();
1453
+ } else if (key === "\u001b[A") { // Up arrow
1454
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
1455
+ renderMenu();
1456
+ } else if (key === "\u001b[B") { // Down arrow
1457
+ selectedIndex = (selectedIndex + 1) % options.length;
1458
+ renderMenu();
1459
+ } else if (key === " ") { // Space
1460
+ options[selectedIndex].selected = !options[selectedIndex].selected;
1461
+ renderMenu();
1462
+ } else if (key === "\r") { // Enter
1463
+ executeInstall();
1464
+ }
1465
+ });
1466
+ }
1467
+
188
1468
  const command = process.argv[2] || "help";
189
1469
  const args = process.argv.slice(3);
190
1470
 
191
1471
  switch (command) {
192
1472
  case "install":
193
1473
  copySkills({ target: parseTarget(args, "both") });
1474
+ seedLeanCtxPolicy(process.cwd());
1475
+ setupHooks();
194
1476
  break;
195
1477
  case "postinstall":
196
1478
  if (process.env.GENESIS_HARNESS_SKIP_POSTINSTALL === "1") {
197
1479
  process.exit(0);
198
1480
  }
199
1481
  copySkills({ quiet: true, target: "both" });
1482
+ const postinstallRoot = resolvePostinstallProjectRoot();
1483
+ seedLeanCtxPolicy(postinstallRoot, { quiet: true });
1484
+ setupHooks(postinstallRoot);
200
1485
  break;
201
1486
  case "verify":
202
1487
  verifySkill(parseTarget(args, "both"));
@@ -211,6 +1496,51 @@ switch (command) {
211
1496
  }
212
1497
  }
213
1498
  break;
1499
+ case "status":
1500
+ showStatus();
1501
+ break;
1502
+ case "docs":
1503
+ showDocsStatus();
1504
+ break;
1505
+ case "docs-gate":
1506
+ runDocsGate();
1507
+ break;
1508
+ case "remember":
1509
+ rememberFact(args[0], args[1]);
1510
+ break;
1511
+ case "recall":
1512
+ recallFacts(args[0]);
1513
+ break;
1514
+ case "forget":
1515
+ forgetFact(args[0]);
1516
+ break;
1517
+ case "prime":
1518
+ primeContext();
1519
+ break;
1520
+ case "leanctx":
1521
+ showLeanCtx();
1522
+ break;
1523
+ case "view-mockup":
1524
+ viewMockupsInteractive(args[0]);
1525
+ break;
1526
+ case "mcp":
1527
+ mcpSetupInteractive();
1528
+ break;
1529
+ case "sync":
1530
+ syncContext();
1531
+ break;
1532
+ case "setup-hooks":
1533
+ setupHooks();
1534
+ break;
1535
+ case "verify-gate":
1536
+ runVerifyGate();
1537
+ break;
1538
+ case "cold-start":
1539
+ runColdStart();
1540
+ break;
1541
+ case "heal":
1542
+ healTest(args.join(" "));
1543
+ break;
214
1544
  case "help":
215
1545
  case "--help":
216
1546
  case "-h":