- T1 (BUG-1160): 「通用读法 · 先看受冲」 opens every turn's methodology once. - T2 (BUG-1161): Chinese lists for parents, children, education, migration, family, annual, general, health; health replaces the English route in the web consultation only. - T3 (BUG-1162): career / marriage / wealth each gain an affliction line. - T4 (BUG-1163): health label 健康, prompt asks about the constitution first. - Single-domain annual / timing turns keep the pre-brief method (CONSULT_READING_BUDGET_BLOCKED): they would exceed the 12,000-character assertions, which this brief may not change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
183 lines
9.2 KiB
TypeScript
183 lines
9.2 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,
|
|
CONDENSED_SHARED_READING_TITLE,
|
|
CONSULT_BRIEF_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,
|
|
);
|
|
// 原值:只排除 Shared mandatory baseline;新值:同时排除每轮一次的「通用读法 · 先看受冲」;
|
|
// 原因:TASK-consult-affliction-reading-20261001 T1 把通用读法放在 sections 最前面,它不是领域清单。
|
|
const domainSections = methodology.sections.filter((section) => (
|
|
!section.title.includes("Shared mandatory baseline") && section.title !== CONDENSED_SHARED_READING_TITLE
|
|
));
|
|
// 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 gets this product's own list, never another route's", () => {
|
|
const methodology = consultationMethodologyForDomains(["education"]);
|
|
assert.ok(methodology);
|
|
// 原值:["education"](路由没声明清单就报缺、不替代);新值:[],学业发本产品自己的中文清单;
|
|
// 原因:TASK-consult-affliction-reading-20261001 决策 D6 对普通对话推翻「报缺不替代」。
|
|
assert.deepEqual(methodology.domains_without_strict_checklist, []);
|
|
const education = methodology.sections.find((section) => section.title.includes("consult-education"));
|
|
assert.ok(education);
|
|
assert.equal(education.source, CONSULT_BRIEF_CHECKLIST_SOURCE);
|
|
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 consultation's Chinese health list in place of health-timing-strict", () => {
|
|
const methodology = consultationMethodologyForDomains(["health"]);
|
|
assert.ok(methodology);
|
|
assert.deepEqual(methodology.domains_without_strict_checklist, []);
|
|
// 原值:标题含 health-timing-strict(路由英文清单);新值:consult-health 中文清单,来源是任务书;
|
|
// 原因:TASK-consult-affliction-reading-20261001 决策 D4/D5,普通对话健康改口径(报告面不变)。
|
|
assert.equal(methodology.sections.some((section) => section.title.includes("health-timing-strict")), false);
|
|
const health = methodology.sections.find((section) => section.title.includes("consult-health"));
|
|
assert.ok(health);
|
|
assert.equal(health.source, CONSULT_BRIEF_CHECKLIST_SOURCE);
|
|
assert.match(health.text, /体质底子/);
|
|
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);
|
|
});
|