Files
Jyotisha/frontend/tests/consultation-methodology.test.ts
T
Jesse_Chen d35828e76d
Independent Staging Quality Gate / validate (push) Successful in 9m18s
Independent Staging Quality Gate / publish (push) Has been cancelled
fix(consult): read the live skill tree instead of a hash-pinned snapshot
Manual SKILL.md updates were blocked by registry sha256 and a byte-equal versions/ gate. Consult now loads the operator-maintained tree; rectification and personal-report stay hashed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-19 10:25:55 +08:00

130 lines
5.7 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
consultationMethodologyForDomains,
markdownSection,
METHODOLOGY_SECTION_MAX_CHARS,
METHODOLOGY_TOTAL_MAX_CHARS,
} from "../src/lib/consultation-methodology.ts";
import { consultationDomainIds } from "../src/lib/consultation-domain-registry.ts";
import { resolveLiveJyotishSkill } from "../src/lib/skill-package-registry.ts";
test("the route's own strict checklist reaches the answer, not just the package listing", () => {
const methodology = consultationMethodologyForDomains(["career"]);
assert.ok(methodology, "career must resolve a methodology");
const career = methodology.sections.find((section) => section.title.includes("career-timing-strict"));
assert.ok(career, "the career strict route must be delivered as its own section");
assert.match(career.source, /strict-workflow-router\.md$/);
// The checklist is only useful if the specific instructions arrive with it.
assert.match(career.text, /D10/);
assert.match(career.text, /Opportunity contact/);
assert.ok(
methodology.sections.some((section) => section.title.includes("Shared mandatory baseline")),
"every route is read against the shared baseline",
);
assert.ok(
methodology.sections.some((section) => /Full-Spectrum Invocation Contract/.test(section.text)),
"the skill's full-spectrum contract must reach the web model",
);
assert.deepEqual(methodology.domains_without_strict_checklist, []);
});
test("a route the skill declares no checklist for is reported, not filled in with another route's", () => {
const methodology = consultationMethodologyForDomains(["education"]);
assert.ok(methodology);
assert.deepEqual(methodology.domains_without_strict_checklist, ["education"]);
assert.ok(
!methodology.sections.some((section) => section.title.includes("timing-strict") || section.title.includes("verification-strict")),
"no strict section may be substituted for a route that has none",
);
assert.ok(
methodology.sections.some((section) => section.title.includes("Shared mandatory baseline")),
"the baseline still applies",
);
assert.ok(
methodology.sections.some((section) => section.title.includes("Full-spectrum")),
"full-spectrum invocation still applies when a domain has no named checklist",
);
});
test("health uses the skill's health-timing-strict checklist", () => {
const methodology = consultationMethodologyForDomains(["health"]);
assert.ok(methodology);
assert.deepEqual(methodology.domains_without_strict_checklist, []);
const health = methodology.sections.find((section) => section.title.includes("health-timing-strict"));
assert.ok(health);
assert.match(health.text, /D6/);
assert.match(health.text, /D30/);
});
test("a multi-domain plan carries every executed route's checklist once", () => {
const methodology = consultationMethodologyForDomains(["career", "timing", "career"]);
assert.ok(methodology);
const titles = methodology.sections.map((section) => section.title);
assert.equal(titles.filter((title) => title.includes("career-timing-strict")).length, 1);
assert.equal(titles.filter((title) => title.includes("event-timing-strict")).length, 1);
});
test("no plan can spend the answer's context on method", () => {
for (const domain of consultationDomainIds) {
const methodology = consultationMethodologyForDomains([domain]);
assert.ok(methodology, `${domain} must resolve`);
const total = methodology.sections.reduce((sum, section) => sum + section.text.length, 0);
assert.ok(
total <= METHODOLOGY_TOTAL_MAX_CHARS,
`${domain} delivered ${total} chars, over the ${METHODOLOGY_TOTAL_MAX_CHARS} budget`,
);
for (const section of methodology.sections) {
assert.ok(section.text.length <= METHODOLOGY_SECTION_MAX_CHARS);
}
}
});
test("the widest legal plan still fits the budget", () => {
const methodology = consultationMethodologyForDomains(["career", "marriage", "wealth", "timing"]);
assert.ok(methodology);
const total = methodology.sections.reduce((sum, section) => sum + section.text.length, 0);
assert.ok(total <= METHODOLOGY_TOTAL_MAX_CHARS, `delivered ${total} chars`);
});
test("further reading offers the references the skill names, and only ones that exist", () => {
const methodology = consultationMethodologyForDomains(["career"]);
assert.ok(methodology);
assert.ok(methodology.further_reading.length > 0, "the skill names references worth reading");
assert.ok(
methodology.further_reading.includes("references/strict-workflow-router.md"),
"the router the skill points at must be reachable on purpose",
);
for (const path of methodology.further_reading) {
assert.match(path, /^references\//);
assert.ok(!path.includes(".."), "no path may escape the package");
}
assert.ok(
methodology.further_reading.length < 100,
"the index exists to replace the package listing, not to reproduce it",
);
});
test("the delivered method is quoted from the live skill tree", () => {
const methodology = consultationMethodologyForDomains(["career"]);
assert.ok(methodology);
assert.equal(methodology.skill, "jyotish-vedic-astrology");
assert.equal(methodology.version, resolveLiveJyotishSkill().version);
assert.equal(methodology.version.includes("/"), false);
});
test("a section is found by heading text so the router may be renumbered", () => {
const source = [
"# Doc",
"## 3. `career-timing-strict`",
"body one",
"## 4. next",
"body two",
].join("\n");
const section = markdownSection(source, "career-timing-strict");
assert.ok(section);
assert.match(section, /body one/);
assert.ok(!section.includes("body two"), "a section stops at the next heading");
assert.equal(markdownSection(source, "absent-route"), null);
});