codex-genesis-harness 0.1.7 → 0.1.9
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 +10 -10
- package/.codebase/DEPENDENCY_GRAPH.md +14 -1
- package/.codebase/IMPLEMENTATION_HANDOFF.md +34 -336
- package/.codebase/KNOWN_PROBLEMS.md +73 -3
- package/.codebase/MODULE_INDEX.md +23 -2
- package/.codebase/PIPELINE_FLOW.md +16 -6
- package/.codebase/RECOVERY_POINTS.md +80 -78
- package/.codebase/TECH_DEBT.md +6 -0
- package/.codebase/TEST_MATRIX.md +8 -3
- package/.codebase/VISUAL_GRAPH.md +127 -0
- package/.codebase/context-policy.json +68 -0
- package/.codebase/memories/lessons_learned.md +63 -0
- package/.codebase/memories/preferences.md +17 -0
- package/.codebase/state.json +156 -17
- 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 +73 -1385
- package/.codex/skills/genesis-harness/agents/openai.yaml +1 -2
- package/.codex/skills/genesis-harness/references/state-machine.md +4 -1
- package/.codex/skills/genesis-harness/references/workflows.md +7 -1
- package/.codex/skills/genesis-harness/scripts/check-docs-sync.sh +3 -3
- package/.codex/skills/genesis-harness/scripts/init-planning.sh +246 -14
- 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-pipeline-orchestration/SKILL.md +15 -3
- 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 +6 -5
- package/CHANGELOG.md +25 -1
- package/README.EN.md +74 -17
- package/README.VI.md +77 -19
- package/README.md +126 -10
- package/VERSION +1 -2
- package/bin/genesis-harness.js +2979 -149
- package/contracts/features/project-registry-schema.json +37 -0
- package/contracts/features/registry-schema.json +15 -0
- package/contracts/observability/agent-run-schema.json +39 -0
- package/contracts/observability/failure-schema.json +35 -0
- package/contracts/ui/auth/login-screen-contract.json +43 -0
- package/features/REGISTRY.md +65 -0
- package/features/SCOPE-template.md +65 -0
- package/fixtures/pipeline/end-to-end-project-lifecycle-fixture.md +39 -0
- package/fixtures/pipeline/feature-completion-fixture.md +26 -0
- package/fixtures/pipeline/run-to-feature-execution-fixture.md +20 -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 +15 -4
- 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-repository-hygiene.js +48 -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 +152 -3
- package/scripts/schema/001-init.sql +129 -0
- package/scripts/schema/002-story-verify.sql +9 -0
- package/scripts/schema/003-tool-registry.sql +15 -0
- package/scripts/schema/004-intervention.sql +15 -0
- package/scripts/scratch_parser.js +49 -0
- package/scripts/spec_visual_sync.js +1 -1
- package/scripts/test_generator.js +2 -2
- package/scripts/transition_state.sh +32 -8
- package/scripts/uninstall.sh +4 -0
- package/scripts/validation_gates.sh +2 -80
- package/scripts/verify.sh +19 -2
- package/tests/fixtures/fixture-index.md +5 -0
- package/tests/integration/cli-smoke.test.js +506 -0
- package/tests/unit/feature_registry.test.js +152 -0
- package/tests/unit/prompt_sentinel.test.js +1 -1
- package/tests/unit/repository_hygiene.test.js +17 -0
- package/tests/unit/spec_visual_sync.test.js +1 -1
- package/tests/unit/state_metadata.test.js +76 -0
- package/tests/unit/test_generator.test.js +1 -1
- package/tests/unit/verify_gate.test.js +25 -0
- package/tests/unit/workflow_contracts.test.js +90 -0
- package/fixtures/tts/tts-fixture-template.md +0 -14
- package/fixtures/videos/video-fixture-template.md +0 -14
- package/playwright/e2e/e2e-template.md +0 -4
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const assert = require("assert");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const { execFileSync } = require("child_process");
|
|
9
|
+
|
|
10
|
+
const repoRoot = path.resolve(__dirname, "..", "..");
|
|
11
|
+
const cli = path.join(repoRoot, "bin", "genesis-harness.js");
|
|
12
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "genesis-cli-smoke-"));
|
|
13
|
+
|
|
14
|
+
function run(args, cwd = tmp) {
|
|
15
|
+
return execFileSync(process.execPath, [cli, ...args], {
|
|
16
|
+
cwd,
|
|
17
|
+
env: {
|
|
18
|
+
...process.env,
|
|
19
|
+
CODEX_HOME: path.join(tmp, "codex-home"),
|
|
20
|
+
GENESIS_HARNESS_HOME: path.join(tmp, "agents-home"),
|
|
21
|
+
GENESIS_HARNESS_SKIP_POSTINSTALL: "1"
|
|
22
|
+
},
|
|
23
|
+
encoding: "utf8"
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function runFailure(args, cwd = tmp) {
|
|
28
|
+
try {
|
|
29
|
+
run(args, cwd);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return `${error.stdout || ""}${error.stderr || ""}`;
|
|
32
|
+
}
|
|
33
|
+
assert.fail(`expected command to fail: ${args.join(" ")}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function runPostinstall(initCwd) {
|
|
37
|
+
return execFileSync(process.execPath, [cli, "postinstall"], {
|
|
38
|
+
cwd: repoRoot,
|
|
39
|
+
env: {
|
|
40
|
+
...process.env,
|
|
41
|
+
CODEX_HOME: path.join(initCwd, "codex-postinstall-home"),
|
|
42
|
+
GENESIS_HARNESS_HOME: path.join(initCwd, "agents-postinstall-home"),
|
|
43
|
+
INIT_CWD: initCwd
|
|
44
|
+
},
|
|
45
|
+
encoding: "utf8"
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fs.mkdirSync(path.join(tmp, ".codebase"), { recursive: true });
|
|
50
|
+
fs.mkdirSync(path.join(tmp, ".planning"), { recursive: true });
|
|
51
|
+
fs.writeFileSync(path.join(tmp, ".codebase", "CURRENT_STATE.md"), "# Current State: TEST\n");
|
|
52
|
+
fs.writeFileSync(path.join(tmp, ".codebase", "API_CONTRACTS.md"), "# API Contracts\n");
|
|
53
|
+
fs.writeFileSync(path.join(tmp, ".planning", "SPEC_CHANGELOG.md"), "# Spec Changelog\n");
|
|
54
|
+
fs.writeFileSync(path.join(tmp, "package.json"), JSON.stringify({ name: "smoke-fixture" }, null, 2));
|
|
55
|
+
|
|
56
|
+
run(["install", "--target", "agents"]);
|
|
57
|
+
const seededPolicyPath = path.join(tmp, ".codebase", "context-policy.json");
|
|
58
|
+
assert(fs.existsSync(seededPolicyPath), "install should seed LeanCTX policy into project .codebase");
|
|
59
|
+
const seededPolicy = JSON.parse(fs.readFileSync(seededPolicyPath, "utf8"));
|
|
60
|
+
assert.strictEqual(seededPolicy.token_budget, 12000, "seeded LeanCTX policy should use package default");
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(
|
|
63
|
+
seededPolicyPath,
|
|
64
|
+
JSON.stringify({ token_budget: 8000, compact_at: 0.7, hard_stop_at: 0.9 }, null, 2)
|
|
65
|
+
);
|
|
66
|
+
run(["install", "--target", "agents"]);
|
|
67
|
+
const preservedPolicy = JSON.parse(fs.readFileSync(seededPolicyPath, "utf8"));
|
|
68
|
+
assert.strictEqual(preservedPolicy.token_budget, 8000, "install must not overwrite customized LeanCTX policy");
|
|
69
|
+
|
|
70
|
+
const postinstallTmp = fs.mkdtempSync(path.join(os.tmpdir(), "genesis-postinstall-smoke-"));
|
|
71
|
+
fs.writeFileSync(path.join(postinstallTmp, "package.json"), JSON.stringify({ name: "postinstall-fixture" }, null, 2));
|
|
72
|
+
runPostinstall(postinstallTmp);
|
|
73
|
+
assert(
|
|
74
|
+
fs.existsSync(path.join(postinstallTmp, ".codebase", "context-policy.json")),
|
|
75
|
+
"postinstall should seed LeanCTX policy when npm exposes INIT_CWD"
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const pathOutput = run(["path"]);
|
|
79
|
+
assert(pathOutput.includes("genesis-harness"), "path should include installed skill paths");
|
|
80
|
+
|
|
81
|
+
const initTmp = fs.mkdtempSync(path.join(os.tmpdir(), "genesis-init-smoke-"));
|
|
82
|
+
fs.writeFileSync(path.join(initTmp, "README.md"), "# Blank project\n");
|
|
83
|
+
const initOutput = run(["init", "--platform", "codex", "--yes"], initTmp);
|
|
84
|
+
assert(initOutput.includes("Initialization Complete"), "init should complete non-interactively");
|
|
85
|
+
assert(
|
|
86
|
+
fs.existsSync(path.join(initTmp, ".planning", "STATE.md")),
|
|
87
|
+
"init should create planning state for empty projects"
|
|
88
|
+
);
|
|
89
|
+
assert(
|
|
90
|
+
fs.existsSync(path.join(initTmp, ".codex", "skills", "genesis-harness", "SKILL.md")),
|
|
91
|
+
"codex init should install packaged skills into the project-local .codex/skills directory"
|
|
92
|
+
);
|
|
93
|
+
assert(
|
|
94
|
+
!fs.existsSync(path.join(initTmp, "codex-home", "skills", "genesis-harness")),
|
|
95
|
+
"codex init should not write packaged skills into CODEX_HOME"
|
|
96
|
+
);
|
|
97
|
+
assert(
|
|
98
|
+
!fs.existsSync(path.join(initTmp, "agents-home", "skills", "genesis-harness")),
|
|
99
|
+
"codex init should not write packaged skills into GENESIS_HARNESS_HOME"
|
|
100
|
+
);
|
|
101
|
+
assert(
|
|
102
|
+
fs.existsSync(path.join(initTmp, ".planning", "phases", "01-discovery-and-qa", "TASKS.md")),
|
|
103
|
+
"init should create a post-foundation discovery phase"
|
|
104
|
+
);
|
|
105
|
+
assert(
|
|
106
|
+
fs.existsSync(path.join(initTmp, ".codebase", "PHASE_DEPENDENCY_MAP.md")),
|
|
107
|
+
"init should create a phase dependency map"
|
|
108
|
+
);
|
|
109
|
+
const initState = fs.readFileSync(path.join(initTmp, ".planning", "STATE.md"), "utf8");
|
|
110
|
+
assert(
|
|
111
|
+
initState.includes("Run discovery Q&A to confirm product approach and tech stack."),
|
|
112
|
+
"init should route the next step to discovery Q&A"
|
|
113
|
+
);
|
|
114
|
+
const qaBrief = fs.readFileSync(path.join(initTmp, ".planning", "INIT_QA.md"), "utf8");
|
|
115
|
+
assert(qaBrief.includes("tech stack"), "init should seed tech stack QA prompts");
|
|
116
|
+
assert(qaBrief.includes("QA sign-off"), "init should seed QA approval prompts");
|
|
117
|
+
const roadmap = fs.readFileSync(path.join(initTmp, ".planning", "ROADMAP.md"), "utf8");
|
|
118
|
+
assert(roadmap.includes("01 Discovery & QA"), "init should add a discovery phase to the roadmap");
|
|
119
|
+
|
|
120
|
+
const ideaTmp = fs.mkdtempSync(path.join(os.tmpdir(), "genesis-init-idea-"));
|
|
121
|
+
const idea =
|
|
122
|
+
"Build a lightweight meal-planning app for busy families that suggests weekly menus, tracks groceries, and works on mobile first.";
|
|
123
|
+
const ideaInitOutput = run(["init", "--platform", "codex", "--yes", "--idea", idea], ideaTmp);
|
|
124
|
+
assert(ideaInitOutput.includes("Initialization Complete"), "init with idea should complete");
|
|
125
|
+
const projectDoc = fs.readFileSync(path.join(ideaTmp, ".planning", "PROJECT.md"), "utf8");
|
|
126
|
+
assert(projectDoc.includes("meal-planning app"), "init with idea should seed project summary from the idea");
|
|
127
|
+
const requirementsDoc = fs.readFileSync(path.join(ideaTmp, ".planning", "REQUIREMENTS.md"), "utf8");
|
|
128
|
+
assert(requirementsDoc.includes("weekly menus"), "init with idea should seed functional requirements from the idea");
|
|
129
|
+
const stackDoc = fs.readFileSync(path.join(ideaTmp, ".planning", "STACK.md"), "utf8");
|
|
130
|
+
assert(stackDoc.includes("Mobile-first"), "init with idea should capture stack clues from the idea");
|
|
131
|
+
const summaryDoc = fs.readFileSync(path.join(ideaTmp, ".planning", "SUMMARY.md"), "utf8");
|
|
132
|
+
assert(summaryDoc.includes("meal-planning app"), "init with idea should carry the idea into the planning summary");
|
|
133
|
+
const initQaDoc = fs.readFileSync(path.join(ideaTmp, ".planning", "INIT_QA.md"), "utf8");
|
|
134
|
+
assert(initQaDoc.includes(idea), "init with idea should embed the original user brief for QA follow-up");
|
|
135
|
+
|
|
136
|
+
const runTmp = fs.mkdtempSync(path.join(os.tmpdir(), "genesis-run-idea-"));
|
|
137
|
+
const runIdea =
|
|
138
|
+
"Create a concierge booking assistant for boutique hotels that helps staff manage guest requests from a tablet.";
|
|
139
|
+
const runOutput = run(
|
|
140
|
+
[
|
|
141
|
+
"run",
|
|
142
|
+
"--platform",
|
|
143
|
+
"codex",
|
|
144
|
+
"--yes",
|
|
145
|
+
"--idea",
|
|
146
|
+
runIdea,
|
|
147
|
+
"--product-approach",
|
|
148
|
+
"Start with a staff-facing web dashboard optimized for tablet use in the lobby.",
|
|
149
|
+
"--primary-user",
|
|
150
|
+
"Front-desk hotel staff",
|
|
151
|
+
"--v1-outcome",
|
|
152
|
+
"Staff can log, prioritize, and resolve guest requests in one queue.",
|
|
153
|
+
"--qa-owner",
|
|
154
|
+
"Operations lead",
|
|
155
|
+
"--backend",
|
|
156
|
+
"Node.js",
|
|
157
|
+
"--frontend",
|
|
158
|
+
"React",
|
|
159
|
+
"--database",
|
|
160
|
+
"PostgreSQL",
|
|
161
|
+
"--deployment",
|
|
162
|
+
"Fly.io",
|
|
163
|
+
"--test-strategy",
|
|
164
|
+
"Node integration tests and Playwright smoke tests",
|
|
165
|
+
"--stack-owner",
|
|
166
|
+
"Tech lead"
|
|
167
|
+
],
|
|
168
|
+
runTmp
|
|
169
|
+
);
|
|
170
|
+
assert(runOutput.includes("Run pipeline complete"), "run should complete the bootstrap pipeline");
|
|
171
|
+
const runProjectDoc = fs.readFileSync(path.join(runTmp, ".planning", "PROJECT.md"), "utf8");
|
|
172
|
+
assert(runProjectDoc.includes("Front-desk hotel staff"), "run should record the primary user");
|
|
173
|
+
assert(runProjectDoc.includes("staff-facing web dashboard"), "run should record the chosen product approach");
|
|
174
|
+
const runStackDoc = fs.readFileSync(path.join(runTmp, ".planning", "STACK.md"), "utf8");
|
|
175
|
+
assert(runStackDoc.includes("Language: Node.js"), "run should fill backend stack details");
|
|
176
|
+
assert(runStackDoc.includes("Framework: React"), "run should fill frontend stack details");
|
|
177
|
+
const runAdrDoc = fs.readFileSync(path.join(runTmp, ".planning", "decisions", "ADR-001-tech-stack.md"), "utf8");
|
|
178
|
+
assert(runAdrDoc.includes("Status: Accepted"), "run should close the tech-stack ADR when answers are provided");
|
|
179
|
+
const runInitQaDoc = fs.readFileSync(path.join(runTmp, ".planning", "INIT_QA.md"), "utf8");
|
|
180
|
+
assert(runInitQaDoc.includes("Operations lead"), "run should record the QA owner");
|
|
181
|
+
assert(runInitQaDoc.includes("Fly.io"), "run should record deployment direction");
|
|
182
|
+
const runRequirementsDoc = fs.readFileSync(path.join(runTmp, ".planning", "REQUIREMENTS.md"), "utf8");
|
|
183
|
+
assert(
|
|
184
|
+
!runRequirementsDoc.includes("guest requests are handled without context loss"),
|
|
185
|
+
"run should not hard-code a hotel-specific user-story outcome into generic planning docs"
|
|
186
|
+
);
|
|
187
|
+
assert(
|
|
188
|
+
runRequirementsDoc.includes("the core workflow can be completed without context loss"),
|
|
189
|
+
"run should use a generic user-story outcome in seeded planning docs"
|
|
190
|
+
);
|
|
191
|
+
const runState = JSON.parse(fs.readFileSync(path.join(runTmp, ".codebase", "state.json"), "utf8"));
|
|
192
|
+
assert.strictEqual(runState.current_state, "IMPLEMENTATION", "run should advance the bootstrap state into feature execution");
|
|
193
|
+
assert(
|
|
194
|
+
runState.pending_tasks.includes("Implement the first feature slice"),
|
|
195
|
+
"run should queue the first implementation slice"
|
|
196
|
+
);
|
|
197
|
+
assert(runState.active_feature, "run should record the active feature path");
|
|
198
|
+
const activeFeatureDir = path.join(runTmp, runState.active_feature);
|
|
199
|
+
assert(fs.existsSync(activeFeatureDir), "run should scaffold the active feature directory");
|
|
200
|
+
assert(
|
|
201
|
+
fs.existsSync(path.join(activeFeatureDir, "SPEC.md")),
|
|
202
|
+
"run should create a feature specification before implementation"
|
|
203
|
+
);
|
|
204
|
+
assert(
|
|
205
|
+
fs.existsSync(path.join(activeFeatureDir, "PLAN.md")),
|
|
206
|
+
"run should create a feature plan before implementation"
|
|
207
|
+
);
|
|
208
|
+
assert(
|
|
209
|
+
fs.existsSync(path.join(activeFeatureDir, "TEST_CONTRACT.md")),
|
|
210
|
+
"run should create a feature test contract before implementation"
|
|
211
|
+
);
|
|
212
|
+
assert(
|
|
213
|
+
fs.existsSync(path.join(activeFeatureDir, "VERIFICATION.md")),
|
|
214
|
+
"run should create feature verification instructions before implementation"
|
|
215
|
+
);
|
|
216
|
+
const activeFeatureSpec = fs.readFileSync(path.join(activeFeatureDir, "SPEC.md"), "utf8");
|
|
217
|
+
assert(activeFeatureSpec.includes("Front-desk hotel staff"), "run should seed the feature spec with the primary user");
|
|
218
|
+
assert(activeFeatureSpec.includes("one queue"), "run should seed the feature spec with the v1 outcome");
|
|
219
|
+
const activeFeaturePlan = fs.readFileSync(path.join(activeFeatureDir, "PLAN.md"), "utf8");
|
|
220
|
+
assert(activeFeaturePlan.includes("scripts/verify.sh"), "run should seed implementation verification commands");
|
|
221
|
+
const activeFeatureContract = fs.readFileSync(path.join(activeFeatureDir, "TEST_CONTRACT.md"), "utf8");
|
|
222
|
+
assert(activeFeatureContract.includes("contracts/ui/"), "run should link the feature test contract to generated UI contracts");
|
|
223
|
+
assert(activeFeatureContract.includes("contracts/api/"), "run should link the feature test contract to generated API contracts");
|
|
224
|
+
const featureIndex = fs.readFileSync(path.join(runTmp, ".planning", "FEATURE_INDEX.md"), "utf8");
|
|
225
|
+
assert(featureIndex.includes(path.basename(activeFeatureDir)), "run should register the active feature in FEATURE_INDEX.md");
|
|
226
|
+
const generatedUiContractPath = path.join(runTmp, "contracts", "ui", path.basename(activeFeatureDir), "screen-contract.json");
|
|
227
|
+
assert(fs.existsSync(generatedUiContractPath), "run should scaffold a UI contract for a UI-capable first feature");
|
|
228
|
+
const generatedUiContract = JSON.parse(fs.readFileSync(generatedUiContractPath, "utf8"));
|
|
229
|
+
assert(generatedUiContract.contract_id.startsWith("UI-"), "run should generate a typed UI contract id");
|
|
230
|
+
assert.strictEqual(generatedUiContract.inputs.route, "/staff-queue", "run should derive a UI route for the first feature");
|
|
231
|
+
assert(
|
|
232
|
+
generatedUiContract.outputs.events.some(event => event.name === "onQueueItemSelect"),
|
|
233
|
+
"run should generate UI events tailored to the first feature"
|
|
234
|
+
);
|
|
235
|
+
const generatedUiFixturePath = path.join(runTmp, "playwright", "fixtures", `${path.basename(activeFeatureDir)}-ui-fixture.md`);
|
|
236
|
+
assert(fs.existsSync(generatedUiFixturePath), "run should scaffold a UI fixture for a UI-capable first feature");
|
|
237
|
+
const generatedUiFixture = fs.readFileSync(generatedUiFixturePath, "utf8");
|
|
238
|
+
assert(generatedUiFixture.includes("/staff-queue"), "run should seed the UI fixture with the derived route");
|
|
239
|
+
const generatedApiContractDir = path.join(runTmp, "contracts", "api", path.basename(activeFeatureDir));
|
|
240
|
+
assert(fs.existsSync(path.join(generatedApiContractDir, "request.json")), "run should scaffold an API request contract for a backend-capable first feature");
|
|
241
|
+
assert(fs.existsSync(path.join(generatedApiContractDir, "response.json")), "run should scaffold an API response contract for a backend-capable first feature");
|
|
242
|
+
const generatedApiRequest = JSON.parse(fs.readFileSync(path.join(generatedApiContractDir, "request.json"), "utf8"));
|
|
243
|
+
assert.strictEqual(generatedApiRequest.method, "POST", "run should generate an API method for the first feature");
|
|
244
|
+
assert.strictEqual(generatedApiRequest.path, "/api/staff-queue/items", "run should derive an API path for the first feature");
|
|
245
|
+
const generatedApiResponse = JSON.parse(fs.readFileSync(path.join(generatedApiContractDir, "response.json"), "utf8"));
|
|
246
|
+
assert.strictEqual(generatedApiResponse.status, 200, "run should generate an expected successful API status");
|
|
247
|
+
assert.strictEqual(generatedApiResponse.body.status, "queued", "run should tailor the API response body to the first feature");
|
|
248
|
+
const generatedApiFixturePath = path.join(runTmp, "fixtures", "api", `${path.basename(activeFeatureDir)}-api-fixture.md`);
|
|
249
|
+
assert(fs.existsSync(generatedApiFixturePath), "run should scaffold an API fixture for a backend-capable first feature");
|
|
250
|
+
const generatedApiFixture = fs.readFileSync(generatedApiFixturePath, "utf8");
|
|
251
|
+
assert(generatedApiFixture.includes("/api/staff-queue/items"), "run should seed the API fixture with the derived endpoint");
|
|
252
|
+
const runArtifactDir = path.join(runTmp, ".runs", runState.session_id);
|
|
253
|
+
assert(
|
|
254
|
+
fs.existsSync(path.join(runArtifactDir, "STATE.json")),
|
|
255
|
+
"run should persist a resumable state artifact for the active session"
|
|
256
|
+
);
|
|
257
|
+
const runArtifactState = JSON.parse(fs.readFileSync(path.join(runArtifactDir, "STATE.json"), "utf8"));
|
|
258
|
+
assert.strictEqual(
|
|
259
|
+
runArtifactState.current_state,
|
|
260
|
+
"IMPLEMENTATION",
|
|
261
|
+
"run artifact state should mirror the active implementation phase"
|
|
262
|
+
);
|
|
263
|
+
assert(
|
|
264
|
+
fs.existsSync(path.join(runArtifactDir, "DISCOVERY.json")),
|
|
265
|
+
"run should persist discovery answers for later resume"
|
|
266
|
+
);
|
|
267
|
+
assert(
|
|
268
|
+
fs.existsSync(path.join(runArtifactDir, "RESUME.md")),
|
|
269
|
+
"run should persist a human-readable resume brief"
|
|
270
|
+
);
|
|
271
|
+
const resumeOutput = run(["resume"], runTmp);
|
|
272
|
+
assert(resumeOutput.includes("Resume session:"), "resume should print the active session summary");
|
|
273
|
+
assert(resumeOutput.includes(runState.session_id), "resume should reference the active session id");
|
|
274
|
+
assert(resumeOutput.includes("IMPLEMENTATION"), "resume should show the current execution state");
|
|
275
|
+
assert(
|
|
276
|
+
resumeOutput.includes(path.basename(activeFeatureDir)),
|
|
277
|
+
"resume should reference the active feature directory"
|
|
278
|
+
);
|
|
279
|
+
assert(
|
|
280
|
+
resumeOutput.includes("Implement the first feature slice"),
|
|
281
|
+
"resume should direct the next implementation task"
|
|
282
|
+
);
|
|
283
|
+
const featureRegistryPath = path.join(runTmp, ".planning", "FEATURE_REGISTRY.json");
|
|
284
|
+
assert(
|
|
285
|
+
fs.existsSync(featureRegistryPath),
|
|
286
|
+
"run should create a machine-readable project feature registry"
|
|
287
|
+
);
|
|
288
|
+
const projectFeatureRegistry = JSON.parse(fs.readFileSync(featureRegistryPath, "utf8"));
|
|
289
|
+
assert.strictEqual(projectFeatureRegistry.features.length, 1, "run should register the first feature");
|
|
290
|
+
assert.strictEqual(
|
|
291
|
+
projectFeatureRegistry.features[0].status,
|
|
292
|
+
"in-progress",
|
|
293
|
+
"the scaffolded first feature should enter the execution queue"
|
|
294
|
+
);
|
|
295
|
+
assert.strictEqual(
|
|
296
|
+
projectFeatureRegistry.project_status,
|
|
297
|
+
"implementation",
|
|
298
|
+
"the project registry should record the implementation lifecycle stage"
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const secondFeatureTitle = "Notify staff when a guest request changes";
|
|
302
|
+
const addFeatureOutput = run(
|
|
303
|
+
[
|
|
304
|
+
"add-feature",
|
|
305
|
+
"--title",
|
|
306
|
+
secondFeatureTitle,
|
|
307
|
+
"--slug",
|
|
308
|
+
"guest-request-notifications",
|
|
309
|
+
"--verify-cmd",
|
|
310
|
+
`${process.execPath} -e "process.exit(0)"`
|
|
311
|
+
],
|
|
312
|
+
runTmp
|
|
313
|
+
);
|
|
314
|
+
assert(addFeatureOutput.includes(secondFeatureTitle), "add-feature should report the queued feature");
|
|
315
|
+
const registryWithQueue = JSON.parse(fs.readFileSync(featureRegistryPath, "utf8"));
|
|
316
|
+
assert.strictEqual(registryWithQueue.features.length, 2, "add-feature should append to the project queue");
|
|
317
|
+
assert.strictEqual(registryWithQueue.features[1].status, "planned", "new features should remain planned");
|
|
318
|
+
assert(
|
|
319
|
+
fs.existsSync(path.join(runTmp, registryWithQueue.features[1].path, "TASKS.md")),
|
|
320
|
+
"add-feature should create an execution packet for the queued feature"
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const nextOutput = run(["next"], runTmp);
|
|
324
|
+
assert(nextOutput.includes(path.basename(activeFeatureDir)), "next should identify the active feature");
|
|
325
|
+
assert(nextOutput.includes("Add the first failing test"), "next should print the next pending action");
|
|
326
|
+
|
|
327
|
+
const missingEvidenceOutput = runFailure(
|
|
328
|
+
["complete-feature", "--verify-cmd", `${process.execPath} -e "process.exit(0)"`],
|
|
329
|
+
runTmp
|
|
330
|
+
);
|
|
331
|
+
assert(
|
|
332
|
+
missingEvidenceOutput.includes("--evidence"),
|
|
333
|
+
"complete-feature should require explicit verification evidence"
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const completionOutput = run(
|
|
337
|
+
[
|
|
338
|
+
"complete-feature",
|
|
339
|
+
"--verify-cmd",
|
|
340
|
+
`${process.execPath} -e "process.exit(0)"`,
|
|
341
|
+
"--evidence",
|
|
342
|
+
"CLI lifecycle smoke verification passed"
|
|
343
|
+
],
|
|
344
|
+
runTmp
|
|
345
|
+
);
|
|
346
|
+
assert(completionOutput.includes("Feature completed"), "complete-feature should report successful completion");
|
|
347
|
+
const promotedState = JSON.parse(fs.readFileSync(path.join(runTmp, ".codebase", "state.json"), "utf8"));
|
|
348
|
+
assert.strictEqual(
|
|
349
|
+
promotedState.current_state,
|
|
350
|
+
"IMPLEMENTATION",
|
|
351
|
+
"complete-feature should keep the project in implementation while queued work remains"
|
|
352
|
+
);
|
|
353
|
+
assert(
|
|
354
|
+
promotedState.active_feature.includes("guest-request-notifications"),
|
|
355
|
+
"complete-feature should promote the next planned feature"
|
|
356
|
+
);
|
|
357
|
+
assert(
|
|
358
|
+
promotedState.metrics.time_to_verified_feature_seconds >= 0,
|
|
359
|
+
"complete-feature should record feature lead-time telemetry"
|
|
360
|
+
);
|
|
361
|
+
const promotedRegistry = JSON.parse(fs.readFileSync(featureRegistryPath, "utf8"));
|
|
362
|
+
assert.strictEqual(
|
|
363
|
+
promotedRegistry.features[0].status,
|
|
364
|
+
"verified",
|
|
365
|
+
"complete-feature should move the active feature to verified"
|
|
366
|
+
);
|
|
367
|
+
assert.strictEqual(
|
|
368
|
+
promotedRegistry.features[0].evidence,
|
|
369
|
+
"CLI lifecycle smoke verification passed",
|
|
370
|
+
"complete-feature should persist verification evidence"
|
|
371
|
+
);
|
|
372
|
+
assert.strictEqual(
|
|
373
|
+
promotedRegistry.features[1].status,
|
|
374
|
+
"in-progress",
|
|
375
|
+
"complete-feature should promote the next queued feature"
|
|
376
|
+
);
|
|
377
|
+
const featureIndexAfterCompletion = fs.readFileSync(path.join(runTmp, ".planning", "FEATURE_INDEX.md"), "utf8");
|
|
378
|
+
assert(
|
|
379
|
+
featureIndexAfterCompletion.includes(`| ${promotedRegistry.features[0].title} | [x] |`),
|
|
380
|
+
"complete-feature should mark the feature complete in FEATURE_INDEX.md"
|
|
381
|
+
);
|
|
382
|
+
const lifecycleRunLog = path.join(
|
|
383
|
+
runTmp,
|
|
384
|
+
"observability",
|
|
385
|
+
"agent-runs",
|
|
386
|
+
`${promotedState.session_id}-feature-complete.json`
|
|
387
|
+
);
|
|
388
|
+
assert(fs.existsSync(lifecycleRunLog), "complete-feature should write an observability run record");
|
|
389
|
+
const nextPromotedOutput = run(["next"], runTmp);
|
|
390
|
+
assert(nextPromotedOutput.includes(secondFeatureTitle), "next should resolve the promoted feature");
|
|
391
|
+
|
|
392
|
+
run(
|
|
393
|
+
[
|
|
394
|
+
"complete-feature",
|
|
395
|
+
"--verify-cmd",
|
|
396
|
+
`${process.execPath} -e "process.exit(0)"`,
|
|
397
|
+
"--evidence",
|
|
398
|
+
"Notification feature verification passed"
|
|
399
|
+
],
|
|
400
|
+
runTmp
|
|
401
|
+
);
|
|
402
|
+
const verificationState = JSON.parse(fs.readFileSync(path.join(runTmp, ".codebase", "state.json"), "utf8"));
|
|
403
|
+
assert.strictEqual(
|
|
404
|
+
verificationState.current_state,
|
|
405
|
+
"VERIFICATION",
|
|
406
|
+
"completing the final feature should move the project to verification"
|
|
407
|
+
);
|
|
408
|
+
assert.strictEqual(verificationState.active_feature, "", "final feature completion should clear active work");
|
|
409
|
+
|
|
410
|
+
const prematureCompletion = runFailure(["complete-project", "--evidence", "Premature close"], runTmp);
|
|
411
|
+
assert(
|
|
412
|
+
prematureCompletion.includes("RELEASE_READY"),
|
|
413
|
+
"complete-project should reject projects that have not passed project verification"
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
const projectVerificationOutput = run(
|
|
417
|
+
[
|
|
418
|
+
"verify-project",
|
|
419
|
+
"--verify-cmd",
|
|
420
|
+
`${process.execPath} -e "process.exit(0)"`,
|
|
421
|
+
"--evidence",
|
|
422
|
+
"Project acceptance suite passed"
|
|
423
|
+
],
|
|
424
|
+
runTmp
|
|
425
|
+
);
|
|
426
|
+
assert(projectVerificationOutput.includes("Project verified"), "verify-project should report success");
|
|
427
|
+
const releaseReadyState = JSON.parse(fs.readFileSync(path.join(runTmp, ".codebase", "state.json"), "utf8"));
|
|
428
|
+
assert.strictEqual(
|
|
429
|
+
releaseReadyState.current_state,
|
|
430
|
+
"RELEASE_READY",
|
|
431
|
+
"verify-project should move the project to release readiness"
|
|
432
|
+
);
|
|
433
|
+
assert(
|
|
434
|
+
fs.existsSync(path.join(runTmp, ".planning", "PROJECT_VERIFICATION.json")),
|
|
435
|
+
"verify-project should persist project-level proof"
|
|
436
|
+
);
|
|
437
|
+
assert(
|
|
438
|
+
fs.existsSync(path.join(runTmp, ".planning", "IMPLEMENTATION_HANDOFF.md")),
|
|
439
|
+
"verify-project should create a final implementation handoff"
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const projectCompletionOutput = run(
|
|
443
|
+
["complete-project", "--evidence", "Release readiness approved"],
|
|
444
|
+
runTmp
|
|
445
|
+
);
|
|
446
|
+
assert(projectCompletionOutput.includes("Project completed"), "complete-project should close release-ready work");
|
|
447
|
+
const completedState = JSON.parse(fs.readFileSync(path.join(runTmp, ".codebase", "state.json"), "utf8"));
|
|
448
|
+
assert.strictEqual(completedState.current_state, "COMPLETED", "complete-project should close project state");
|
|
449
|
+
const historyLength = completedState.history.length;
|
|
450
|
+
const eventsPath = path.join(runArtifactDir, "EVENTS.jsonl");
|
|
451
|
+
assert(fs.existsSync(eventsPath), "lifecycle transitions should create append-only run events");
|
|
452
|
+
const eventCount = fs.readFileSync(eventsPath, "utf8").trim().split("\n").length;
|
|
453
|
+
assert(eventCount >= 5, "event history should include queue, feature, verification, and completion transitions");
|
|
454
|
+
|
|
455
|
+
const repeatedCompletionOutput = run(
|
|
456
|
+
["complete-project", "--evidence", "Release readiness approved"],
|
|
457
|
+
runTmp
|
|
458
|
+
);
|
|
459
|
+
assert(repeatedCompletionOutput.includes("already completed"), "complete-project should be idempotent");
|
|
460
|
+
const repeatedCompletionState = JSON.parse(fs.readFileSync(path.join(runTmp, ".codebase", "state.json"), "utf8"));
|
|
461
|
+
assert.strictEqual(
|
|
462
|
+
repeatedCompletionState.history.length,
|
|
463
|
+
historyLength,
|
|
464
|
+
"idempotent completion should not duplicate state history"
|
|
465
|
+
);
|
|
466
|
+
assert.strictEqual(
|
|
467
|
+
fs.readFileSync(eventsPath, "utf8").trim().split("\n").length,
|
|
468
|
+
eventCount,
|
|
469
|
+
"idempotent completion should not duplicate lifecycle events"
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
const auditOutput = run(["pipeline-audit"], runTmp);
|
|
473
|
+
assert(auditOutput.includes("Pipeline audit passed"), "pipeline-audit should validate the completed lifecycle");
|
|
474
|
+
|
|
475
|
+
const statusOutput = run(["status"]);
|
|
476
|
+
assert(statusOutput.includes("GENESIS HARNESS - STATUS REPORT"), "status should render status report");
|
|
477
|
+
|
|
478
|
+
const docsOutput = run(["docs"]);
|
|
479
|
+
assert(docsOutput.includes("GENESIS HARNESS - DOCUMENTATION REPORT"), "docs should render docs report");
|
|
480
|
+
|
|
481
|
+
const leanCtxOutput = run(["leanctx"]);
|
|
482
|
+
assert(leanCtxOutput.includes("LeanCTX Policy"), "leanctx should render policy report");
|
|
483
|
+
assert(leanCtxOutput.includes("Token budget: 8000"), "leanctx should read project policy");
|
|
484
|
+
assert(leanCtxOutput.includes("Command wrapper:"), "leanctx should report command wrapper detection");
|
|
485
|
+
assert(leanCtxOutput.includes("rtk optional"), "leanctx should treat rtk as optional");
|
|
486
|
+
assert(leanCtxOutput.includes("genesis-harness sync"), "leanctx should include portable sync command");
|
|
487
|
+
|
|
488
|
+
const primeOutput = run(["prime"]);
|
|
489
|
+
assert(primeOutput.includes("LeanCTX Policy"), "prime should include LeanCTX policy");
|
|
490
|
+
assert(primeOutput.includes("rtk optional"), "prime should document optional wrapper behavior");
|
|
491
|
+
|
|
492
|
+
const gateOutput = run(["docs-gate"]);
|
|
493
|
+
assert(
|
|
494
|
+
gateOutput.includes("Docs sync check passed") || gateOutput.includes("docs sync check is limited"),
|
|
495
|
+
"docs-gate should pass in fixture"
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
run(["sync"]);
|
|
499
|
+
const visual = fs.readFileSync(path.join(tmp, ".codebase", "VISUAL_GRAPH.md"), "utf8");
|
|
500
|
+
assert(visual.includes("Harness Relationship Map"), "sync should generate harness relationship map");
|
|
501
|
+
assert(visual.includes("genesis-harness docs-gate"), "sync graph should include docs-gate");
|
|
502
|
+
assert(!visual.includes("Đăng nhập"), "sync graph must not emit stale sample roadmap");
|
|
503
|
+
assert(!visual.includes("src/auth.js"), "sync graph must not emit stale sample source path");
|
|
504
|
+
assert(visual.includes("```mermaid"), "sync graph should contain mermaid fences");
|
|
505
|
+
|
|
506
|
+
console.log("cli smoke passed");
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* feature_registry.test.js
|
|
6
|
+
* L08 – Feature List as Harness Primitive
|
|
7
|
+
* L11 – Observability Bên Trong Harness
|
|
8
|
+
*
|
|
9
|
+
* Tests must FAIL before implementation (Red phase).
|
|
10
|
+
* Run: node tests/unit/feature_registry.test.js
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const assert = require("assert");
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
|
|
17
|
+
const repoRoot = path.resolve(__dirname, "..", "..");
|
|
18
|
+
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
// L08 — Feature Registry checks
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
const REGISTRY_PATH = path.join(repoRoot, "features", "REGISTRY.md");
|
|
24
|
+
const REGISTRY_SCHEMA_PATH = path.join(repoRoot, "contracts", "features", "registry-schema.json");
|
|
25
|
+
|
|
26
|
+
// Test 1: features/REGISTRY.md phải tồn tại
|
|
27
|
+
assert(
|
|
28
|
+
fs.existsSync(REGISTRY_PATH),
|
|
29
|
+
"L08: features/REGISTRY.md must exist as machine-readable feature primitive"
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Test 2: REGISTRY.md phải chứa section machine-readable features table
|
|
33
|
+
const registryContent = fs.readFileSync(REGISTRY_PATH, "utf8");
|
|
34
|
+
assert(
|
|
35
|
+
registryContent.includes("| id |"),
|
|
36
|
+
"L08: REGISTRY.md must have a machine-readable table with 'id' column"
|
|
37
|
+
);
|
|
38
|
+
assert(
|
|
39
|
+
registryContent.includes("| status |"),
|
|
40
|
+
"L08: REGISTRY.md must have a 'status' column"
|
|
41
|
+
);
|
|
42
|
+
assert(
|
|
43
|
+
registryContent.includes("| verify_cmd |"),
|
|
44
|
+
"L08: REGISTRY.md must have a 'verify_cmd' column per feature"
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Test 3: Mỗi feature phải có trạng thái hợp lệ
|
|
48
|
+
const validStatuses = ["planned", "in-progress", "done", "verified", "deprecated"];
|
|
49
|
+
const tableRows = registryContent
|
|
50
|
+
.split("\n")
|
|
51
|
+
.filter(line => /^\|\s*F\d+\s*\|/.test(line)); // Only data rows from Feature Table (id starts with F)
|
|
52
|
+
assert(
|
|
53
|
+
tableRows.length > 0,
|
|
54
|
+
"L08: REGISTRY.md must have at least one feature entry (id must start with F, e.g. F001)"
|
|
55
|
+
);
|
|
56
|
+
for (const row of tableRows) {
|
|
57
|
+
const cols = row.split("|").map(c => c.trim()).filter(Boolean);
|
|
58
|
+
if (cols.length < 2) continue;
|
|
59
|
+
const status = cols[1];
|
|
60
|
+
assert(
|
|
61
|
+
validStatuses.includes(status),
|
|
62
|
+
`L08: Invalid status '${status}' in REGISTRY.md — must be one of: ${validStatuses.join(", ")}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Test 4: contracts/features/registry-schema.json phải tồn tại
|
|
67
|
+
assert(
|
|
68
|
+
fs.existsSync(REGISTRY_SCHEMA_PATH),
|
|
69
|
+
"L08: contracts/features/registry-schema.json must exist"
|
|
70
|
+
);
|
|
71
|
+
const schema = JSON.parse(fs.readFileSync(REGISTRY_SCHEMA_PATH, "utf8"));
|
|
72
|
+
assert(schema.version, "L08: registry-schema.json must have a version field");
|
|
73
|
+
assert(schema.required_columns, "L08: registry-schema.json must define required_columns");
|
|
74
|
+
assert(
|
|
75
|
+
Array.isArray(schema.required_columns) && schema.required_columns.includes("id"),
|
|
76
|
+
"L08: required_columns must include 'id'"
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Test 5: REGISTRY.md phải được đề cập trong MODULE_INDEX.md
|
|
80
|
+
const moduleIndex = fs.readFileSync(
|
|
81
|
+
path.join(repoRoot, ".codebase", "MODULE_INDEX.md"),
|
|
82
|
+
"utf8"
|
|
83
|
+
);
|
|
84
|
+
assert(
|
|
85
|
+
moduleIndex.includes("features/REGISTRY.md"),
|
|
86
|
+
"L08: features/REGISTRY.md must be listed in .codebase/MODULE_INDEX.md"
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
90
|
+
// L11 — Observability Live Data checks
|
|
91
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
const OBS_SCHEMA_PATH = path.join(repoRoot, "contracts", "observability", "agent-run-schema.json");
|
|
94
|
+
const OBS_SAMPLE_PATH = path.join(repoRoot, "observability", "agent-runs", "sample-run.json");
|
|
95
|
+
const OBS_FAILURE_SCHEMA_PATH = path.join(repoRoot, "contracts", "observability", "failure-schema.json");
|
|
96
|
+
const OBS_FAILURE_SAMPLE_PATH = path.join(repoRoot, "observability", "failures", "sample-failure.json");
|
|
97
|
+
const OBS_DECISION_SAMPLE_PATH = path.join(repoRoot, "observability", "decision-logs", "sample-decision.md");
|
|
98
|
+
|
|
99
|
+
// Test 6: contracts/observability/ phải có agent-run-schema.json
|
|
100
|
+
assert(
|
|
101
|
+
fs.existsSync(OBS_SCHEMA_PATH),
|
|
102
|
+
"L11: contracts/observability/agent-run-schema.json must exist"
|
|
103
|
+
);
|
|
104
|
+
const agentRunSchema = JSON.parse(fs.readFileSync(OBS_SCHEMA_PATH, "utf8"));
|
|
105
|
+
assert(agentRunSchema.required_fields, "L11: agent-run-schema.json must define required_fields");
|
|
106
|
+
const requiredRunFields = ["session_id", "timestamp", "skill", "phase", "outcome", "evidence"];
|
|
107
|
+
for (const field of requiredRunFields) {
|
|
108
|
+
assert(
|
|
109
|
+
agentRunSchema.required_fields.includes(field),
|
|
110
|
+
`L11: agent-run-schema.json must require field '${field}'`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Test 7: observability/agent-runs/ phải có ít nhất 1 sample entry
|
|
115
|
+
assert(
|
|
116
|
+
fs.existsSync(OBS_SAMPLE_PATH),
|
|
117
|
+
"L11: observability/agent-runs/sample-run.json must exist as live sample"
|
|
118
|
+
);
|
|
119
|
+
const sampleRun = JSON.parse(fs.readFileSync(OBS_SAMPLE_PATH, "utf8"));
|
|
120
|
+
for (const field of requiredRunFields) {
|
|
121
|
+
assert(
|
|
122
|
+
sampleRun[field] !== undefined,
|
|
123
|
+
`L11: sample-run.json is missing required field '${field}'`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Test 8: contracts/observability/failure-schema.json phải tồn tại
|
|
128
|
+
assert(
|
|
129
|
+
fs.existsSync(OBS_FAILURE_SCHEMA_PATH),
|
|
130
|
+
"L11: contracts/observability/failure-schema.json must exist"
|
|
131
|
+
);
|
|
132
|
+
const failureSchema = JSON.parse(fs.readFileSync(OBS_FAILURE_SCHEMA_PATH, "utf8"));
|
|
133
|
+
assert(failureSchema.required_fields, "L11: failure-schema.json must define required_fields");
|
|
134
|
+
|
|
135
|
+
// Test 9: observability/failures/ phải có sample failure entry
|
|
136
|
+
assert(
|
|
137
|
+
fs.existsSync(OBS_FAILURE_SAMPLE_PATH),
|
|
138
|
+
"L11: observability/failures/sample-failure.json must exist"
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Test 10: observability/decision-logs/ phải có sample thực tế (không chỉ template)
|
|
142
|
+
assert(
|
|
143
|
+
fs.existsSync(OBS_DECISION_SAMPLE_PATH),
|
|
144
|
+
"L11: observability/decision-logs/sample-decision.md must exist as actual decision record"
|
|
145
|
+
);
|
|
146
|
+
const decisionContent = fs.readFileSync(OBS_DECISION_SAMPLE_PATH, "utf8");
|
|
147
|
+
assert(
|
|
148
|
+
!decisionContent.includes("What changed."),
|
|
149
|
+
"L11: sample-decision.md must be a real decision record, not just the blank template"
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
console.log("feature_registry tests passed");
|