fix(consult): read the live skill tree instead of a hash-pinned snapshot
Independent Staging Quality Gate / validate (push) Successful in 9m18s
Independent Staging Quality Gate / publish (push) Has been cancelled

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>
This commit is contained in:
Jesse_Chen
2026-08-19 10:25:55 +08:00
parent 233c728176
commit d35828e76d
14 changed files with 315 additions and 109 deletions
@@ -7,6 +7,7 @@ import {
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"]);
@@ -104,11 +105,12 @@ test("further reading offers the references the skill names, and only ones that
);
});
test("the delivered method is quoted from the pinned skill version", () => {
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.match(methodology.version, /^\d+\.\d+\.\d+$/);
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", () => {
@@ -534,9 +534,14 @@ test("instrumentation retains the Skill guard and starts a singleton Node worker
const productionAdapter = readFileSync(new URL("../src/lib/personal-report-worker.ts", import.meta.url), "utf8");
assert.match(instrumentation, /verifyAllActiveSkillPackages\(\)/);
assert.match(instrumentation, /resolveLiveJyotishSkill\(\)/);
assert.match(instrumentation, /startPersonalReportWorker\(\)/);
assert.ok(
instrumentation.indexOf("verifyAllActiveSkillPackages()")
< instrumentation.indexOf("resolveLiveJyotishSkill()"),
);
assert.ok(
instrumentation.indexOf("resolveLiveJyotishSkill()")
< instrumentation.indexOf("startPersonalReportWorker()"),
);
assert.match(productionAdapter, /jyotishaPersonalReportWorker/);
+16 -13
View File
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { existsSync, readFileSync, realpathSync } from "node:fs";
import { join, resolve } from "node:path";
import test from "node:test";
import { Agent } from "@mastra/core/agent";
import {
@@ -17,9 +17,9 @@ function probeAgent(options: Record<string, unknown>) {
return new Agent({ id: "skill-binding-probe", name: "Probe", model, instructions: "x", ...options } as never);
}
function publishedSkillBody() {
const published = readFileSync(resolve(jyotishSkillPackage.resolvedPath, "SKILL.md"), "utf8");
return published.slice(published.indexOf("---", 3) + 4).trim();
function liveSkillBody() {
const live = readFileSync(resolve(jyotishSkillPackage.resolvedPath, "SKILL.md"), "utf8");
return live.slice(live.indexOf("---", 3) + 4).trim();
}
function boundMethodText() {
@@ -29,20 +29,23 @@ function boundMethodText() {
return jyotishSkillMethodBlock.slice(jyotishSkillMethodBlock.indexOf("\n", opened) + 1, closed).trim();
}
test("the method the model follows is the published one, not the working tree's", () => {
const body = publishedSkillBody();
test("the method the model follows is the live skill, not a hashed snapshot", () => {
const body = liveSkillBody();
const bound = boundMethodText();
assert.match(jyotishSkillMethodBlock, new RegExp(`version="${jyotishSkillPackage.version}"`));
// Every bound line still comes from the hash-checked package. The route into
// the loader is that same package, so skill_read can only reach what the
// registry hash covers.
assert.match(jyotishSkillMethodBlock, new RegExp(`name="${jyotishSkillPackage.name}"`));
assert.doesNotMatch(jyotishSkillMethodBlock, /<jyotish-skill name="[^"]+" version="/);
for (const line of bound.split("\n")) {
if (line.length === 0) continue;
assert.ok(body.includes(line), line.slice(0, 80));
}
assert.ok(jyotishSkillRuntimePath.includes(jyotishSkillPackage.sha256));
assert.ok(jyotishSkillRuntimePath.endsWith(jyotishSkillPackage.name));
assert.equal(jyotishSkillRuntimePath.includes("/versions/"), false);
assert.equal(existsSync(join(jyotishSkillRuntimePath, "versions")), false);
assert.equal(
realpathSync(join(jyotishSkillRuntimePath, "SKILL.md")),
realpathSync(join(jyotishSkillPackage.resolvedPath, "SKILL.md")),
);
});
test("the bound method is the runtime excerpt, not the maintainer manual", () => {
@@ -50,7 +53,7 @@ test("the bound method is the runtime excerpt, not the maintainer manual", () =>
// answering contract and the repo-construction notes. Binding the whole
// file is what pushed Mastra past its 500-line warning; the excerpt keeps
// the contract and drops the notes.
const body = publishedSkillBody();
const body = liveSkillBody();
const bound = boundMethodText();
for (const heading of ["商业运行时路由", "关联技法完整调取", "强制工作流", "五层硬约束", "核心方法论", "注意事项", "commercial_skill_truth_overlay"]) {
+65 -21
View File
@@ -3,10 +3,12 @@ import { createHash } from "node:crypto";
import {
mkdirSync,
mkdtempSync,
readFileSync,
realpathSync,
rmSync,
symlinkSync,
writeFileSync,
existsSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
@@ -17,7 +19,8 @@ import {
createSkillPackageRegistry,
resolveActiveSkillPackage,
resolveExactSkillPackage,
resolveSkillPackageRuntimePath,
resolveLiveJyotishSkill,
resolveLiveJyotishSkillRuntimePath,
resolveSkillPackageVersion,
verifyAllActiveSkillPackages,
type SkillPackageIdentity,
@@ -75,16 +78,11 @@ function identity(
};
}
test("checked-in registry verifies all three active packages and returns absolute directories", () => {
test("checked-in registry verifies hashed product packages and leaves consult on the live skill", () => {
const packages = verifyAllActiveSkillPackages({ projectRoot });
assert.deepEqual(
packages.map(({ name, version, sha256 }) => ({ name, version, sha256 })),
[
{
name: "jyotish-vedic-astrology",
version: "6.9.14",
sha256: "c44e6f500c671d18dd47d9714610b766fbeabe00ca5e495e5225bc2c97f5f105",
},
{
name: "jyotish-birth-time-rectification",
version: "10.0.2",
@@ -106,21 +104,21 @@ test("checked-in registry verifies all three active packages and returns absolut
}
assert.equal(packages[0]?.sourceCommit, null);
assert.equal(packages[1]?.sourceCommit, null);
assert.equal(packages[2]?.sourceCommit, null);
const active = resolveActiveSkillPackage("jyotish-vedic-astrology", {
projectRoot,
});
assert.equal(active.version, "6.9.14");
const runtimePath = resolveSkillPackageRuntimePath(active);
assert.equal(basename(runtimePath), active.name);
assert.equal(realpathSync(runtimePath), active.resolvedPath);
assert.notEqual(runtimePath, active.resolvedPath);
assert.deepEqual(
resolveExactSkillPackage(active.name, active.version, active.sha256, {
projectRoot,
}),
active,
assert.throws(
() => resolveActiveSkillPackage("jyotish-vedic-astrology", { projectRoot }),
/No active skill package found/,
);
const live = resolveLiveJyotishSkill({ projectRoot });
assert.equal(live.name, "jyotish-vedic-astrology");
assert.equal(basename(live.resolvedPath), "jyotish-vedic-astrology");
assert.equal(live.resolvedPath, resolve(projectRoot, "skills/jyotish-vedic-astrology"));
const liveRuntime = resolveLiveJyotishSkillRuntimePath(live);
assert.equal(basename(liveRuntime), live.name);
assert.equal(existsSync(join(liveRuntime, "versions")), false);
assert.equal(
realpathSync(join(liveRuntime, "SKILL.md")),
realpathSync(join(live.resolvedPath, "SKILL.md")),
);
const deprecatedRectification = resolveExactSkillPackage(
"jyotish-birth-time-rectification",
@@ -489,3 +487,49 @@ test("invalid hash, commit, and non-directory package paths fail closed", (t) =>
/must be a directory/,
);
});
test("live consult skill reads a hand-updated tree without a registry hash", (t) => {
const f = fixture(t);
const dir = join(f.root, "skills", "jyotish-vedic-astrology");
mkdirSync(dir, { recursive: true });
writeFileSync(
join(dir, "SKILL.md"),
"---\nname: jyotish-vedic-astrology\nversion: 9.9.9\n---\n# live method\n",
);
mkdirSync(join(dir, "references"));
writeFileSync(join(dir, "references", "note.md"), "live reference\n");
mkdirSync(join(dir, "versions", "6.9.14"), { recursive: true });
writeFileSync(join(dir, "versions", "6.9.14", "SKILL.md"), "# stale snapshot\n");
const live = resolveLiveJyotishSkill({ projectRoot: f.root, skillPath: dir });
assert.equal(live.version, "9.9.9");
assert.equal(live.resolvedPath, realpathSync(dir));
const runtime = resolveLiveJyotishSkillRuntimePath(live);
assert.equal(existsSync(join(runtime, "versions")), false);
assert.match(readFileSync(join(runtime, "SKILL.md"), "utf8"), /live method/);
assert.equal(readFileSync(join(runtime, "references", "note.md"), "utf8"), "live reference\n");
});
test("live consult skill rejects a path outside the project", (t) => {
const f = fixture(t);
const dir = join(f.root, "skills", "jyotish-vedic-astrology");
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "SKILL.md"), "---\nname: jyotish-vedic-astrology\n---\n# live\n");
const outside = mkdtempSync(join(tmpdir(), "jyotish-skill-outside-"));
t.after(() => rmSync(outside, { recursive: true, force: true }));
assert.throws(
() => resolveLiveJyotishSkill({ projectRoot: f.root, skillPath: outside }),
/escapes the project root/,
);
});
test("live consult skill rejects a directory that is not named jyotish-vedic-astrology", (t) => {
const f = fixture(t);
const dir = join(f.root, "skills", "other-skill");
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "SKILL.md"), "---\nname: jyotish-vedic-astrology\n---\n# live\n");
assert.throws(
() => resolveLiveJyotishSkill({ projectRoot: f.root, skillPath: dir }),
/must be named jyotish-vedic-astrology/,
);
});
@@ -173,6 +173,7 @@ test("railway web image uses Next standalone runtime output", () => {
assert.match(config, /outputFileTracingRoot: repositoryRoot/);
assert.match(startupGuard, /process\.env\.NEXT_RUNTIME !== "nodejs"/);
assert.match(startupGuard, /verifyAllActiveSkillPackages\(\)/);
assert.match(startupGuard, /resolveLiveJyotishSkill\(\)/);
assert.match(dockerfile, /ARG NEXT_DEPLOYMENT_ID\n/);
assert.match(
dockerfile,
@@ -184,6 +185,10 @@ test("railway web image uses Next standalone runtime output", () => {
assert.match(dockerfile, /COPY --from=build \/app\/frontend\/\.next\/static \/app\/frontend\/\.next\/static/);
assert.match(dockerfile, /COPY --from=build \/app\/frontend\/public \/app\/frontend\/public/);
assert.match(dockerfile, /COPY --from=build \/app\/frontend\/db \/app\/frontend\/db/);
assert.match(dockerfile, /COPY --from=build \/app\/SKILL.md \/app\/SKILL.md/);
assert.match(dockerfile, /COPY --from=build \/app\/assets \/app\/assets/);
assert.match(dockerfile, /COPY --from=build \/app\/references \/app\/references/);
assert.match(dockerfile, /COPY --from=build \/app\/scripts \/app\/scripts/);
assert.match(dockerfile, /COPY --from=build \/app\/skills \/app\/skills/);
assert.match(
dockerfile,