TASK-consult-evidence-card-v2-20260927 T4 (方案 B). Career, marriage and wealth hand the answer model a 5-7 line condensed checklist (frontend/src/lib/consultation-condensed-checklist.ts) in place of the full strict route plus event-judgment file: must-see items (all on the card), the three layers (接触 / 结构性机会 / 公开落地; 心动接触 / 关系成对 / 社会法律落地; 挣钱机会 / 真实增长 / 到账变现), the forbidden statements (5L 大运 ≠ 法律婚, 金星过本命月 ≠ 心动月, Punarphoo 不得写成结婚, 土星回到本命月是观察项), the gender-unknown rule, and the lookups (KP blocked). The shared baseline still travels; timing / health keep their full router sections; the router, the event-judgment files, the bound system method and the skill are unchanged (no skill version change). Only the consultation tool builds the methodology, so reports and other surfaces are unaffected (source test). Single-domain model-visible size, 3 public AA charts x (10 questions + annual + timing): career 9.8K, marriage 10.5K, wealth 9.9K, annual 11.5K, timing 11.3K; all <= 12,000. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
165 lines
7.9 KiB
TypeScript
165 lines
7.9 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 { CONDENSED_CHECKLIST_SOURCE } from "../src/lib/consultation-condensed-checklist.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");
|
|
// Former: source was references/strict-workflow-router.md and the text the full
|
|
// route ("Opportunity contact" …), plus event_judgment_career.md as a second section.
|
|
// New: the condensed consultation checklist, condensed from both, as one section.
|
|
// Reason: TASK-consult-evidence-card-v2-20260927 T4 (方案 B, consultation only).
|
|
assert.equal(career.source, CONDENSED_CHECKLIST_SOURCE);
|
|
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, /接触/);
|
|
assert.ok(
|
|
methodology.sections.some((section) => section.title.includes("Shared mandatory baseline")),
|
|
"every route is read against the shared baseline",
|
|
);
|
|
// Former: Full-spectrum invocation and Event judgment skeleton were tool-result sections.
|
|
// New: those two live in the cached system block; sections keep the domain checklists.
|
|
// Reason: they were resent with every tool result outside any cache prefix.
|
|
assert.equal(
|
|
methodology.sections.filter((section) => (
|
|
section.title.includes("Full-spectrum") || section.title.includes("Event judgment")
|
|
)).length,
|
|
0,
|
|
);
|
|
const domainSections = methodology.sections.filter((section) => !section.title.includes("Shared mandatory baseline"));
|
|
// Former: 2 (strict route + event judgment). New: 1 (condensed list). Reason: as above.
|
|
assert.equal(domainSections.length, 1);
|
|
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",
|
|
);
|
|
// Former: full-spectrum still arrived as a methodology section for domains with no checklist.
|
|
// New: full-spectrum is bound in the system block; this plan only reports the missing checklist.
|
|
assert.equal(
|
|
methodology.sections.filter((section) => section.title.includes("Full-spectrum")).length,
|
|
0,
|
|
);
|
|
});
|
|
|
|
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("wealth uses finance-timing-strict as the live checklist, with the wealth alias in the heading", () => {
|
|
const methodology = consultationMethodologyForDomains(["wealth"]);
|
|
assert.ok(methodology);
|
|
assert.deepEqual(methodology.domains_without_strict_checklist, []);
|
|
const wealth = methodology.sections.find((section) => section.title.includes("finance-timing-strict"));
|
|
assert.ok(wealth, "wealth must deliver finance-timing-strict, not the retired primary name");
|
|
// Former: the full router section, whose heading names the legacy alias (wealth-timing-strict).
|
|
// New: the condensed consultation checklist condensed from that section, titled by the live name.
|
|
// Reason: TASK-consult-evidence-card-v2-20260927 T4 (方案 B, consultation only).
|
|
assert.equal(wealth.source, CONDENSED_CHECKLIST_SOURCE);
|
|
assert.match(wealth.text, /D2/);
|
|
assert.ok(
|
|
!methodology.sections.some((section) => (
|
|
section.title.includes("wealth-timing-strict") && !section.title.includes("finance-timing-strict")
|
|
)),
|
|
"the old wealth route name is an alias, not a second checklist",
|
|
);
|
|
});
|
|
|
|
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);
|
|
});
|