codex-genesis-harness 0.1.7 → 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.
- package/.codebase/COMPRESSED_CONTEXT.md +80 -0
- package/.codebase/CURRENT_STATE.md +37 -11
- package/.codebase/DEPENDENCY_GRAPH.md +14 -1
- package/.codebase/IMPLEMENTATION_HANDOFF.md +34 -336
- package/.codebase/KNOWN_PROBLEMS.md +54 -3
- package/.codebase/MODULE_INDEX.md +8 -0
- package/.codebase/PIPELINE_FLOW.md +7 -5
- package/.codebase/RECOVERY_POINTS.md +17 -78
- package/.codebase/TECH_DEBT.md +6 -0
- package/.codebase/TEST_MATRIX.md +4 -3
- package/.codebase/VISUAL_GRAPH.md +127 -0
- package/.codebase/context-policy.json +68 -0
- package/.codebase/memories/lessons_learned.md +21 -0
- package/.codebase/memories/preferences.md +17 -0
- package/.codebase/state.json +45 -24
- package/.codex/skills/genesis-architecture/SKILL.md +5 -0
- package/.codex/skills/genesis-debug-guide/SKILL.md +10 -4
- package/.codex/skills/genesis-docs-automation/SKILL.md +52 -973
- package/.codex/skills/genesis-executing-plans/SKILL.md +54 -0
- package/.codex/skills/genesis-executing-plans/agents/openai.yaml +6 -0
- package/.codex/skills/genesis-executing-plans/checklists/.gitkeep +0 -0
- package/.codex/skills/genesis-executing-plans/examples/.gitkeep +0 -0
- package/.codex/skills/genesis-executing-plans/templates/.gitkeep +0 -0
- package/.codex/skills/genesis-harness/SKILL.md +64 -1385
- package/.codex/skills/genesis-harness/scripts/check-docs-sync.sh +3 -3
- package/.codex/skills/genesis-harness/scripts/init-planning.sh +1 -1
- package/.codex/skills/genesis-new-design/SKILL.md +4 -1
- package/.codex/skills/genesis-new-design/agents/openai.yaml +2 -0
- package/.codex/skills/genesis-observability-automation/SKILL.md +69 -303
- package/.codex/skills/genesis-observability-automation/references/common-mistakes-and-recovery.md +84 -0
- package/.codex/skills/genesis-observability-automation/references/workflow-phases.md +78 -0
- package/.codex/skills/genesis-performance-profiling/SKILL.md +1 -22
- package/.codex/skills/genesis-performance-profiling/agents/openai.yaml +1 -1
- package/.codex/skills/genesis-planning/SKILL.md +6 -1
- package/.codex/skills/genesis-release/SKILL.md +5 -0
- package/.codex/skills/genesis-research-first/SKILL.md +6 -0
- package/.codex/skills/genesis-spec-propagation/SKILL.md +52 -504
- package/.codex/skills/genesis-test-driven-development/SKILL.md +55 -0
- package/.codex/skills/genesis-test-driven-development/agents/openai.yaml +6 -0
- package/.codex/skills/genesis-test-driven-development/checklists/.gitkeep +0 -0
- package/.codex/skills/genesis-test-driven-development/examples/.gitkeep +0 -0
- package/.codex/skills/genesis-test-driven-development/templates/.gitkeep +0 -0
- package/.codex/skills/genesis-upgrade-design/SKILL.md +4 -2
- package/.codex/skills/genesis-upgrade-design/agents/openai.yaml +2 -0
- package/.codex/skills/genesis-using-git-worktrees/SKILL.md +54 -0
- package/.codex/skills/genesis-using-git-worktrees/agents/openai.yaml +6 -0
- package/.codex/skills/genesis-using-git-worktrees/checklists/.gitkeep +0 -0
- package/.codex/skills/genesis-using-git-worktrees/examples/.gitkeep +0 -0
- package/.codex/skills/genesis-using-git-worktrees/templates/.gitkeep +0 -0
- package/.codex/skills/genesis-verification-before-completion/SKILL.md +53 -0
- package/.codex/skills/genesis-verification-before-completion/agents/openai.yaml +6 -0
- package/.codex/skills/genesis-verification-before-completion/checklists/.gitkeep +0 -0
- package/.codex/skills/genesis-verification-before-completion/examples/.gitkeep +0 -0
- package/.codex/skills/genesis-verification-before-completion/templates/.gitkeep +0 -0
- package/.codex/skills/spec-impact-engine/SKILL.md +77 -500
- package/.codex/skills/spec-impact-engine/checklists/checklist.md +10 -0
- package/.codex-plugin/plugin.json +3 -4
- package/CHANGELOG.md +4 -1
- package/README.EN.md +32 -17
- package/README.VI.md +35 -19
- package/README.md +48 -10
- package/VERSION +1 -1
- package/bin/genesis-harness.js +735 -5
- package/contracts/features/registry-schema.json +15 -0
- package/contracts/observability/agent-run-schema.json +34 -0
- package/contracts/observability/failure-schema.json +35 -0
- package/contracts/ui/auth/login-screen-contract.json +43 -0
- package/features/REGISTRY.md +63 -0
- package/features/SCOPE-template.md +65 -0
- package/fixtures/planning/MOCKUP_PROMPT_TEMPLATE.md +16 -0
- package/observability/agent-runs/sample-run.json +13 -0
- package/observability/decision-logs/sample-decision.md +43 -0
- package/observability/failures/sample-failure.json +12 -0
- package/package.json +9 -3
- package/playwright/e2e/app-template.spec.js +37 -0
- package/playwright/e2e/auth/login-screen.spec.js +65 -0
- package/playwright/e2e/web-template.spec.js +28 -0
- package/scripts/check-scope.sh +100 -0
- package/scripts/cold-start-check.js +133 -0
- package/scripts/install.sh +4 -0
- package/scripts/prompt_sentinel.js +35 -4
- package/scripts/run-evals.sh +119 -3
- package/scripts/scratch_parser.js +49 -0
- package/scripts/spec_visual_sync.js +1 -1
- package/scripts/test_generator.js +2 -2
- package/scripts/uninstall.sh +4 -0
- package/scripts/verify.sh +16 -1
- package/tests/integration/cli-smoke.test.js +103 -0
- package/tests/unit/feature_registry.test.js +152 -0
- package/tests/unit/prompt_sentinel.test.js +1 -1
- package/tests/unit/spec_visual_sync.test.js +1 -1
- package/tests/unit/test_generator.test.js +1 -1
- package/playwright/e2e/e2e-template.md +0 -4
package/bin/genesis-harness.js
CHANGED
|
@@ -27,7 +27,11 @@ const skillNames = [
|
|
|
27
27
|
"genesis-observability-automation",
|
|
28
28
|
"genesis-research-first",
|
|
29
29
|
"genesis-release",
|
|
30
|
-
"spec-impact-engine"
|
|
30
|
+
"spec-impact-engine",
|
|
31
|
+
"genesis-executing-plans",
|
|
32
|
+
"genesis-test-driven-development",
|
|
33
|
+
"genesis-verification-before-completion",
|
|
34
|
+
"genesis-using-git-worktrees"
|
|
31
35
|
];
|
|
32
36
|
const legacySkillNames = ["project-genesis-harness"];
|
|
33
37
|
const sourceRoot = path.join(packageRoot, ".codex", "skills");
|
|
@@ -47,16 +51,26 @@ Usage:
|
|
|
47
51
|
genesis-harness path
|
|
48
52
|
genesis-harness status Show implementation status & skills inventory
|
|
49
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
|
|
50
57
|
genesis-harness remember [cat] "<msg>" Remember a persistent project fact/insight (Bead)
|
|
51
58
|
genesis-harness recall [query] Recall and search remembered project facts
|
|
52
59
|
genesis-harness forget <id> Forget/delete a fact by its unique 6-char ID
|
|
53
60
|
genesis-harness prime Generate the token-minimized Agent Priming Prompt
|
|
61
|
+
genesis-harness leanctx Show token budget policy and portable command guidance
|
|
54
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
|
|
55
67
|
|
|
56
68
|
Environment:
|
|
57
69
|
CODEX_HOME=/custom/.codex Override Codex home
|
|
58
70
|
GENESIS_HARNESS_HOME=/custom/.agents Override modern skills home
|
|
59
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
|
|
60
74
|
`;
|
|
61
75
|
console.log(text.trim());
|
|
62
76
|
process.exit(exitCode);
|
|
@@ -122,6 +136,49 @@ function copySkills({ quiet = false, target = "both" } = {}) {
|
|
|
122
136
|
if (!quiet) console.log("Restart Codex, then invoke: Use $genesis-harness");
|
|
123
137
|
}
|
|
124
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
|
+
|
|
125
182
|
function uninstallSkills(target = "both") {
|
|
126
183
|
for (const root of targetRoots(target)) {
|
|
127
184
|
for (const skillName of [...skillNames, ...legacySkillNames]) {
|
|
@@ -165,6 +222,119 @@ function resolveBash() {
|
|
|
165
222
|
return "bash";
|
|
166
223
|
}
|
|
167
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
|
+
|
|
168
338
|
function chmodScripts(dir) {
|
|
169
339
|
if (!fs.existsSync(dir)) return;
|
|
170
340
|
for (const entry of fs.readdirSync(dir)) {
|
|
@@ -240,7 +410,7 @@ function showStatus() {
|
|
|
240
410
|
}
|
|
241
411
|
|
|
242
412
|
// 3. Skills Inventory
|
|
243
|
-
console.log("\n\x1b[1m\x1b[32m[+] Skills Inventory Check (Exactly
|
|
413
|
+
console.log("\n\x1b[1m\x1b[32m[+] Skills Inventory Check (Exactly 25 core skills):\x1b[0m");
|
|
244
414
|
let found = 0;
|
|
245
415
|
let mismatched = 0;
|
|
246
416
|
for (const skillName of skillNames) {
|
|
@@ -304,7 +474,7 @@ function showDocsStatus() {
|
|
|
304
474
|
}
|
|
305
475
|
}
|
|
306
476
|
console.log(` Files: \x1b[33m${found.join(", ")}\x1b[0m`);
|
|
307
|
-
|
|
477
|
+
|
|
308
478
|
// Print request structure preview if request.json exists
|
|
309
479
|
const reqPath = path.join(apiDir, endpoint, "request.json");
|
|
310
480
|
if (fs.existsSync(reqPath)) {
|
|
@@ -596,12 +766,19 @@ function primeContext() {
|
|
|
596
766
|
out.push("3. **Single source**: Avoid duplicating plans across multi-line markdown logs; use `genesis-harness remember` to store critical project coordinates.");
|
|
597
767
|
out.push("4. **TDD Pattern**: Create or update failing tests in `tests/` before making changes to public behaviors.");
|
|
598
768
|
out.push("");
|
|
769
|
+
out.push("## 🪶 7. LeanCTX Policy");
|
|
770
|
+
out.push(buildLeanCtxReport(process.cwd()));
|
|
771
|
+
out.push("");
|
|
599
772
|
out.push("---");
|
|
600
773
|
out.push("");
|
|
601
774
|
|
|
602
775
|
console.log(out.join("\n"));
|
|
603
776
|
}
|
|
604
777
|
|
|
778
|
+
function showLeanCtx() {
|
|
779
|
+
console.log(buildLeanCtxReport(process.cwd()));
|
|
780
|
+
}
|
|
781
|
+
|
|
605
782
|
function openFileNatively(filePath) {
|
|
606
783
|
if (process.platform === "win32") {
|
|
607
784
|
const cp = spawnSync("cmd.exe", ["/c", "start", "", filePath], { shell: true });
|
|
@@ -611,7 +788,7 @@ function openFileNatively(filePath) {
|
|
|
611
788
|
if (process.platform === "linux") {
|
|
612
789
|
cmd = "xdg-open";
|
|
613
790
|
}
|
|
614
|
-
|
|
791
|
+
|
|
615
792
|
const cp = spawnSync(cmd, [filePath]);
|
|
616
793
|
return cp.status === 0;
|
|
617
794
|
}
|
|
@@ -703,7 +880,7 @@ function viewMockupsInteractive(arg) {
|
|
|
703
880
|
console.log(" \x1b[1m\x1b[32m[+] LAUNCHED SYSTEM VIEW FOR:\x1b[0m \x1b[1m" + selected.title + "\x1b[0m\n");
|
|
704
881
|
console.log(` - \x1b[1mMockup File:\x1b[0m ${selected.fileName}`);
|
|
705
882
|
console.log(` - \x1b[1mFolder Path:\x1b[0m ${path.dirname(selected.fullPath)}`);
|
|
706
|
-
|
|
883
|
+
|
|
707
884
|
let sizeText = "Unknown";
|
|
708
885
|
try {
|
|
709
886
|
const stats = fs.statSync(selected.fullPath);
|
|
@@ -764,18 +941,547 @@ function viewMockupsInteractive(arg) {
|
|
|
764
941
|
});
|
|
765
942
|
}
|
|
766
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
|
+
|
|
767
1468
|
const command = process.argv[2] || "help";
|
|
768
1469
|
const args = process.argv.slice(3);
|
|
769
1470
|
|
|
770
1471
|
switch (command) {
|
|
771
1472
|
case "install":
|
|
772
1473
|
copySkills({ target: parseTarget(args, "both") });
|
|
1474
|
+
seedLeanCtxPolicy(process.cwd());
|
|
1475
|
+
setupHooks();
|
|
773
1476
|
break;
|
|
774
1477
|
case "postinstall":
|
|
775
1478
|
if (process.env.GENESIS_HARNESS_SKIP_POSTINSTALL === "1") {
|
|
776
1479
|
process.exit(0);
|
|
777
1480
|
}
|
|
778
1481
|
copySkills({ quiet: true, target: "both" });
|
|
1482
|
+
const postinstallRoot = resolvePostinstallProjectRoot();
|
|
1483
|
+
seedLeanCtxPolicy(postinstallRoot, { quiet: true });
|
|
1484
|
+
setupHooks(postinstallRoot);
|
|
779
1485
|
break;
|
|
780
1486
|
case "verify":
|
|
781
1487
|
verifySkill(parseTarget(args, "both"));
|
|
@@ -796,6 +1502,9 @@ switch (command) {
|
|
|
796
1502
|
case "docs":
|
|
797
1503
|
showDocsStatus();
|
|
798
1504
|
break;
|
|
1505
|
+
case "docs-gate":
|
|
1506
|
+
runDocsGate();
|
|
1507
|
+
break;
|
|
799
1508
|
case "remember":
|
|
800
1509
|
rememberFact(args[0], args[1]);
|
|
801
1510
|
break;
|
|
@@ -808,9 +1517,30 @@ switch (command) {
|
|
|
808
1517
|
case "prime":
|
|
809
1518
|
primeContext();
|
|
810
1519
|
break;
|
|
1520
|
+
case "leanctx":
|
|
1521
|
+
showLeanCtx();
|
|
1522
|
+
break;
|
|
811
1523
|
case "view-mockup":
|
|
812
1524
|
viewMockupsInteractive(args[0]);
|
|
813
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;
|
|
814
1544
|
case "help":
|
|
815
1545
|
case "--help":
|
|
816
1546
|
case "-h":
|