Files
truthmark/tests/templates/generated-surfaces.test.ts
c92b46ad65 feat: harden init routing and generated lifecycle (#33)
* feat: harden init routing and generated lifecycle

* test: align restored coverage with platform routing

* docs: translate localized uninstall guidance

* test: complete init routing and lifecycle coverage

* docs: record completed OpenSpec tasks

* chore: archive implemented init lifecycle OpenSpec change

* chore: remove OpenSpec configuration

* docs: clarify platform omission reconciliation

---------

Co-authored-by: MerlinH <merlinh221@gmail.com>
2026-07-12 20:48:09 +10:00

588 lines
22 KiB
TypeScript

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, it } from "node:test";
import { expect } from "expect";
import { createDefaultConfig } from "../../src/config/defaults.js";
import { renderTruthSyncSkillBody } from "../../src/agents/truth-sync.js";
import { renderAgentsBlock } from "../../src/templates/agents-block.js";
import {
renderGeneratedSurfaces,
renderGeneratedSurfaceCatalog,
type GeneratedSurfaceOwner,
} from "../../src/templates/generated-surfaces.js";
const platformOwners = (owners: GeneratedSurfaceOwner[]) =>
owners.filter(
(owner): owner is Extract<GeneratedSurfaceOwner, { platform: string }> =>
"platform" in owner,
);
const allPlatforms = [
"codex",
"opencode",
"claude-code",
"github-copilot",
"antigravity",
"cursor",
] as const;
const portalPaths = [
".agents/skills/truthmark-portal/SKILL.md",
".agents/skills/truthmark-portal/agents/openai.yaml",
".opencode/skills/truthmark-portal/SKILL.md",
".claude/skills/truthmark-portal/SKILL.md",
".github/skills/truthmark-portal/SKILL.md",
".github/prompts/truthmark-portal.prompt.md",
".antigravity/rules/truthmark-portal.md",
".cursor/skills/truthmark-portal/SKILL.md",
];
const readOnlyProcedurePaths = [
".agents/skills/truthmark-check/support/procedure.md",
".opencode/skills/truthmark-check/support/procedure.md",
".claude/skills/truthmark-check/support/procedure.md",
".github/skills/truthmark-check/support/procedure.md",
];
const syncProcedurePaths = [
".agents/skills/truthmark-sync/support/procedure.md",
".opencode/skills/truthmark-sync/support/procedure.md",
".claude/skills/truthmark-sync/support/procedure.md",
".github/skills/truthmark-sync/support/procedure.md",
];
const staleWriteAuthorizingLaneText =
"before writing canonical truth docs, classify the request or change as product-lane, engineering-lane, both-lane, or ambiguous";
describe("Truthmark Portal generated surfaces", () => {
it("keeps checked-in read-only procedures free of write-authorizing lane wording", () => {
for (const procedurePath of readOnlyProcedurePaths) {
const content = readFileSync(join(process.cwd(), procedurePath), "utf8");
expect(content).not.toContain(staleWriteAuthorizingLaneText);
expect(content).toContain(
"classify the request or changed surface as product-lane, engineering-lane, both-lane, or ambiguous for reporting only",
);
}
});
it("keeps checked-in Sync procedures on the cheap product-truth decision", () => {
for (const procedurePath of syncProcedurePaths) {
const content = readFileSync(join(process.cwd(), procedurePath), "utf8");
expect(content).toContain("Product truth decision");
expect(content).toContain(
"ask whether a user-visible promise, capability boundary, API contract, acceptance criterion, or explicit user/product evidence changed",
);
expect(content).toContain(
"if no, default to engineering truth under docs/truthmark/engineering for internal implementation changes",
);
expect(content).toContain(
"Product truth is opt-in for externally visible promises, product boundaries, APIs, acceptance criteria, or explicit user/product evidence.",
);
expect(content).toContain("User-provided decisions/rationale");
expect(content).not.toContain(staleWriteAuthorizingLaneText);
expect(content).not.toContain(
"classify lane impact as product-lane, engineering-lane, both-lane, or ambiguous before writing",
);
}
});
it("omits generic optional CLI validation from generated user-facing workflow surfaces", () => {
const config = createDefaultConfig();
config.platforms = [...allPlatforms];
const generatedSurfaces = renderGeneratedSurfaces(config);
const publicWorkflowSurfaces = generatedSurfaces.filter(
(surface) =>
surface.path.endsWith("/SKILL.md") ||
surface.path.startsWith(".github/prompts/") ||
surface.path.startsWith(".antigravity/rules/"),
);
const forbiddenText = [
"## Optional local CLI validation",
"truthmark workflow instructions --json",
["truthmark", "workflow", "status", "--json"].join(" "),
"workflow instructions --json",
["workflow", "status", "--json"].join(" "),
"live preflight",
"OpenSpec-style",
"proposal/spec/task",
"spec lifecycle",
"archive/apply",
"truthmark/changes",
];
expect(publicWorkflowSurfaces.length).toBeGreaterThan(0);
for (const surface of publicWorkflowSurfaces) {
for (const text of forbiddenText) {
expect(surface.content).not.toContain(text);
}
}
});
it("keeps generated runtime surfaces free of repeated host/support overhead", () => {
const config = createDefaultConfig();
config.platforms = [...allPlatforms];
const surfaces = renderGeneratedSurfaces(config);
const runtimeSurfaces = surfaces.filter(
(surface) =>
surface.path.startsWith(".agents/") ||
surface.path.startsWith(".opencode/") ||
surface.path.startsWith(".claude/") ||
surface.path.startsWith(".github/") ||
surface.path.startsWith(".antigravity/") ||
surface.path.startsWith(".cursor/"),
);
const crossHostInvocationText = [
"Invocations:",
"OpenCode /skill truthmark-",
"Codex /truthmark-",
"Claude Code /truthmark-",
"GitHub Copilot /truthmark-",
"Antigravity @truthmark-",
"Cursor @truthmark-",
];
for (const surface of runtimeSurfaces) {
for (const text of crossHostInvocationText) {
expect(surface.content).not.toContain(text);
}
}
const skillEntrypoints = runtimeSurfaces.filter((surface) =>
surface.path.endsWith("/SKILL.md"),
);
for (const surface of skillEntrypoints) {
expect(surface.content).toContain("Progressive disclosure:");
expect(surface.content).not.toContain("Read support/");
}
const flatRules = runtimeSurfaces.filter(
(surface) =>
surface.path.startsWith(".antigravity/rules/") ||
surface.path.startsWith(".cursor/rules/"),
);
expect(flatRules.length).toBeGreaterThan(0);
for (const surface of flatRules) {
expect(surface.content).not.toContain("Quick procedure:");
expect(surface.content).not.toContain("support/procedure.md");
expect(surface.content).not.toContain("support/report-template.md");
expect(surface.content).not.toContain("support/subagents-and-leases.md");
expect(surface.content).toContain("## Procedure");
expect(surface.content).toContain("## Report Template");
}
});
it("keeps compact workflow-specific validation and procedure guidance", () => {
const config = createDefaultConfig();
config.platforms = [...allPlatforms];
const byPath = new Map(
renderGeneratedSurfaces(config).map((surface) => [
surface.path,
surface.content,
]),
);
const syncSkill =
byPath.get(".agents/skills/truthmark-sync/SKILL.md") ?? "";
const syncProcedure =
byPath.get(".agents/skills/truthmark-sync/support/procedure.md") ?? "";
expect(syncSkill).toContain(
"Use this skill automatically before finishing",
);
expect(syncSkill).not.toContain("Parent workflow:");
expect(syncSkill).toContain("support/procedure.md");
expect(syncSkill).toContain("support/report-template.md");
expect(byPath.get(".opencode/skills/truthmark-sync/SKILL.md")).toContain(
"Use this skill automatically before finishing",
);
expect(syncProcedure).toContain("Code verification is parent-owned");
expect(syncProcedure).toContain(
"truthmark validate sync-report <report-file> --json",
);
expect(
byPath.has(".agents/skills/truthmark-sync/helper-manifest.yml"),
).toBe(false);
expect(
byPath.has(".agents/skills/truthmark-sync/support/helper-policy.md"),
).toBe(false);
expect(byPath.has(".agents/skills/truthmark-preview/SKILL.md")).toBe(false);
expect(byPath.has(".github/prompts/truthmark-preview.prompt.md")).toBe(
false,
);
expect(byPath.has(".gemini/commands/truthmark/preview.toml")).toBe(false);
expect(byPath.has("GEMINI.md")).toBe(false);
expect(byPath.has(".gemini/skills/truthmark-sync/SKILL.md")).toBe(false);
expect(byPath.has(".gemini/commands/truthmark/sync.toml")).toBe(false);
expect(byPath.has(".antigravity/rules/truthmark-sync.md")).toBe(true);
expect(byPath.has(".cursor/skills/truthmark-sync/SKILL.md")).toBe(true);
expect(
byPath.has(".cursor/skills/truthmark-sync/support/procedure.md"),
).toBe(true);
expect(
byPath.has(".cursor/skills/truthmark-sync/support/report-template.md"),
).toBe(true);
expect(
byPath.has(".agents/skills/truthmark-preview/agents/openai.yaml"),
).toBe(false);
});
it("does not render unused repo-local agent package copies", () => {
const config = createDefaultConfig();
config.platforms = [...allPlatforms];
const paths = renderGeneratedSurfaces(config).map(
(surface) => surface.path,
);
expect(paths.some((path) => path.startsWith(".truthmark/agent/"))).toBe(
false,
);
expect(paths).toContain(".agents/skills/truthmark-sync/SKILL.md");
expect(paths).toContain(
".agents/skills/truthmark-sync/support/procedure.md",
);
expect(paths).toContain(
".opencode/skills/truthmark-sync/support/procedure.md",
);
});
it("renders host skill packages with colocated native resources", () => {
const config = createDefaultConfig();
config.platforms = [...allPlatforms];
const byPath = new Map(
renderGeneratedSurfaces(config).map((surface) => [
surface.path,
surface.content,
]),
);
const claudeProcedure =
byPath.get(".claude/skills/truthmark-sync/support/procedure.md") ?? "";
const codexProcedure =
byPath.get(".agents/skills/truthmark-sync/support/procedure.md") ?? "";
const opencodeReport =
byPath.get(
".opencode/skills/truthmark-sync/support/report-template.md",
) ?? "";
expect(claudeProcedure).toContain("Parent workflow:");
expect(codexProcedure).toContain("Parent workflow:");
expect(opencodeReport).toContain("Changed code reviewed:");
expect(opencodeReport).toContain("Decision/rationale captured:");
expect(claudeProcedure).not.toContain(
"truthmark:adapter-mode=expanded-adapter",
);
});
it("does not teach Sync to update bootstrap routing as behavior truth", () => {
const config = createDefaultConfig();
config.platforms = [...allPlatforms];
const renderedSurfaces = renderGeneratedSurfaces(config);
const generatedSyncReportSurfaces = renderedSurfaces.filter(
(surface) =>
surface.path.endsWith("truthmark-sync/support/report-template.md") ||
surface.path === ".antigravity/rules/truthmark-sync.md" ||
surface.path ===
".cursor/skills/truthmark-sync/support/report-template.md",
);
const checkedInSyncReportPaths = [
".agents/skills/truthmark-sync/support/report-template.md",
".opencode/skills/truthmark-sync/support/report-template.md",
".claude/skills/truthmark-sync/support/report-template.md",
".github/skills/truthmark-sync/support/report-template.md",
".antigravity/rules/truthmark-sync.md",
".cursor/skills/truthmark-sync/support/report-template.md",
];
const surfacesToCheck = [
{
path: "renderTruthSyncSkillBody",
content: renderTruthSyncSkillBody(config),
},
...generatedSyncReportSurfaces,
...checkedInSyncReportPaths.map((path) => ({
path,
content: readFileSync(join(process.cwd(), path), "utf8"),
})),
];
expect(generatedSyncReportSurfaces.length).toBeGreaterThan(0);
for (const surface of surfacesToCheck) {
expect(surface.content).toContain(
"docs/truthmark/routes/areas/authentication.md",
);
expect(surface.content).toContain(
"docs/truthmark/engineering/behaviors/session-timeout.md",
);
expect(surface.content).toContain(
"Changed code maps only to the provisional bootstrap route.",
);
expect(surface.content).toContain(
"Run Truth Structure for src/auth/** before updating behavior truth.",
);
expect(surface.content).not.toContain(
"- Target truth docs: docs/truthmark/engineering/repository/bootstrap-routing.md",
);
expect(surface.content).not.toContain(
[
"Truth docs updated:",
"- docs/truthmark/engineering/repository/bootstrap-routing.md",
].join("\n"),
);
expect(surface.content).not.toContain(
"Session timeout behavior is documented in the mapped repository truth doc.",
);
}
});
it("omits Portal surfaces and AGENTS wording when disabled", () => {
const config = createDefaultConfig();
const paths = renderGeneratedSurfaces(config).map(
(surface) => surface.path,
);
expect(config.truthmark.generated.portal.enabled).toBe(false);
for (const portalPath of portalPaths) {
expect(paths).not.toContain(portalPath);
}
expect(renderAgentsBlock(config)).not.toContain("Truthmark Portal");
});
it("renders Portal surfaces for all configured platforms when enabled", () => {
const config = createDefaultConfig();
config.platforms = [...allPlatforms];
config.truthmark.generated.portal = {
enabled: true,
};
const surfaces = renderGeneratedSurfaces(config);
const byPath = new Map(
surfaces.map((surface) => [surface.path, surface.content]),
);
for (const portalPath of portalPaths) {
expect(byPath.has(portalPath)).toBe(true);
}
const portalSkill =
byPath.get(".agents/skills/truthmark-portal/SKILL.md") ?? "";
const portalProcedure =
byPath.get(".agents/skills/truthmark-portal/support/procedure.md") ?? "";
const copilotPrompt =
byPath.get(".github/prompts/truthmark-portal.prompt.md") ?? "";
const antigravityRule =
byPath.get(".antigravity/rules/truthmark-portal.md") ?? "";
const cursorSkill =
byPath.get(".cursor/skills/truthmark-portal/SKILL.md") ?? "";
const cursorProcedure =
byPath.get(".cursor/skills/truthmark-portal/support/procedure.md") ?? "";
const agentsBlock = renderAgentsBlock(config);
for (const text of [portalSkill, portalProcedure]) {
expect(text).toContain("manual-only");
expect(text).toContain("Markdown remains canonical");
expect(text).toContain("does not require the truthmark CLI");
expect(text).toContain("docs/truthmark/generated/portal");
expect(text).toContain("docs/truthmark/templates/portal.html");
expect(text).toContain("no remote dependencies");
expect(text).toContain("no .truthmark/index.json dependency");
expect(text).toContain("source provenance");
expect(text).toContain("generated non-canonical static files");
}
expect(copilotPrompt).toContain(
"This prompt is the GitHub Copilot entrypoint for Truthmark Portal.",
);
expect(copilotPrompt).toContain(".github/skills/truthmark-portal/SKILL.md");
expect(antigravityRule).toContain(
"This rule is the Antigravity entrypoint for Truthmark Portal.",
);
expect(cursorSkill).toContain("Use as a Cursor Agent Skill.");
expect(cursorSkill).toContain(".cursor/skills/");
expect(cursorSkill).toContain("Progressive disclosure:");
expect(cursorProcedure).toContain("manual-only");
expect(portalProcedure).toContain("replace the entire output directory");
expect(portalProcedure).toContain("fixed Portal output directory only");
expect(portalProcedure).not.toContain("custom Portal output");
expect(agentsBlock).toContain(
"Truthmark Portal is a separate manual-only presentation workflow",
);
expect(agentsBlock).toContain("docs/truthmark/generated/portal/");
expect(agentsBlock).toContain("Markdown remains canonical");
});
it("maps platform-derived instruction files to AGENTS.md and CLAUDE.md with exact-path dedupe", () => {
const config = createDefaultConfig();
config.platforms = ["codex", "opencode", "claude-code"];
const allSurfaces = renderGeneratedSurfaces(config);
const allPaths = allSurfaces.map((surface) => surface.path);
const catalog = renderGeneratedSurfaceCatalog(config);
const agentsEntry = catalog.find((entry) => entry.path === "AGENTS.md");
const claudeEntry = catalog.find((entry) => entry.path === "CLAUDE.md");
expect(allPaths).toContain("AGENTS.md");
expect(allPaths).toContain("CLAUDE.md");
expect(new Set(allPaths).size).toBe(allPaths.length);
expect(allPaths.filter((path) => path === "AGENTS.md")).toHaveLength(1);
expect(allPaths.filter((path) => path === "CLAUDE.md")).toHaveLength(1);
expect(agentsEntry).toBeDefined();
const agentsPlatforms =
agentsEntry === undefined ? [] : platformOwners(agentsEntry.owners).map((owner) => owner.platform);
expect(new Set(agentsPlatforms).size).toBe(2);
expect(
agentsPlatforms.sort(),
).toEqual(["codex", "opencode"]);
expect(claudeEntry).toBeDefined();
expect(claudeEntry?.owners).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "platform",
platform: "claude-code",
}),
]),
);
expect(renderGeneratedSurfaces({ ...config, platforms: [] })).toEqual([]);
});
it("builds an ownership catalog with stable content signatures across all platforms", () => {
const config = createDefaultConfig();
const normalized = (content: string): string =>
content.endsWith("\n") ? content : `${content}\n`;
config.platforms = [...allPlatforms];
const catalog = renderGeneratedSurfaceCatalog(config);
const configForPlatformAndPortal = (
platform: (typeof allPlatforms)[number],
portalEnabled: boolean,
) => ({
...config,
truthmark: {
...config.truthmark,
generated: {
...config.truthmark.generated,
portal: { enabled: portalEnabled },
},
},
platforms: [platform],
});
const platformSurfaceContents = new Map<string, Set<string>>();
const addSurfaceContents = (surfaces: ReturnType<typeof renderGeneratedSurfaces>) => {
for (const surface of surfaces) {
const signatures = platformSurfaceContents.get(surface.path) ?? new Set();
signatures.add(normalized(surface.content));
platformSurfaceContents.set(surface.path, signatures);
}
};
for (const platform of allPlatforms) {
addSurfaceContents(renderGeneratedSurfaces(configForPlatformAndPortal(platform, false)));
addSurfaceContents(renderGeneratedSurfaces(configForPlatformAndPortal(platform, true)));
}
for (const entry of catalog) {
const expectedContents = platformSurfaceContents.get(entry.path);
if (expectedContents === undefined) continue;
expect(entry.recognizedContents.length).toBeGreaterThan(0);
for (const content of entry.recognizedContents) {
expect(expectedContents).toContain(content);
}
}
});
it("classifies shared renderer outputs as managed blocks and keeps all-platform ownership stable", () => {
const config = createDefaultConfig();
config.platforms = [...allPlatforms];
config.truthmark.generated.portal.enabled = true;
const surfaces = renderGeneratedSurfaces(config);
const managedBlockPaths = new Set(
["AGENTS.md", "CLAUDE.md", ".github/copilot-instructions.md"].filter(
(surfacePath) =>
surfaces.some((surface) => surface.path === surfacePath),
),
);
for (const surface of surfaces) {
if (managedBlockPaths.has(surface.path)) {
expect(surface.managedBlock).toBe(true);
} else {
expect(surface).not.toHaveProperty("managedBlock");
}
}
const catalog = renderGeneratedSurfaceCatalog(config);
const agentsEntry = catalog.find((entry) => entry.path === "AGENTS.md");
const claudeEntry = catalog.find((entry) => entry.path === "CLAUDE.md");
const copilotEntry = catalog.find(
(entry) => entry.path === ".github/copilot-instructions.md",
);
expect(
agentsEntry === undefined
? []
: platformOwners(agentsEntry.owners).map((owner) => owner.platform).sort(),
).toEqual(["codex", "opencode"].sort());
expect(
claudeEntry === undefined
? []
: platformOwners(claudeEntry.owners).map((owner) => owner.platform).sort(),
).toEqual(["claude-code"]);
expect(
copilotEntry === undefined
? []
: platformOwners(copilotEntry.owners).map((owner) => owner.platform).sort(),
).toEqual([
"github-copilot",
]);
expect(agentsEntry?.recognizedContents.length).toBeGreaterThanOrEqual(1);
expect(copilotEntry?.recognizedContents.length).toBeGreaterThanOrEqual(1);
});
it("keeps portal-specific host surfaces when enabled and removes them only for removed hosts", () => {
const enabledConfig = createDefaultConfig();
enabledConfig.platforms = [...allPlatforms];
enabledConfig.truthmark.generated.portal.enabled = true;
const allEnabled = renderGeneratedSurfaces(enabledConfig);
const allEnabledPaths = allEnabled.map((surface) => surface.path);
expect(
allEnabledPaths.includes(".opencode/skills/truthmark-portal/SKILL.md"),
).toBe(true);
expect(
allEnabledPaths.includes(".github/prompts/truthmark-portal.prompt.md"),
).toBe(true);
const oneHostRemoved = createDefaultConfig();
oneHostRemoved.platforms = [...allPlatforms.filter((platform) => platform !== "github-copilot")];
oneHostRemoved.truthmark.generated.portal.enabled = true;
const oneHostPaths = renderGeneratedSurfaces(oneHostRemoved).map(
(surface) => surface.path,
);
expect(
oneHostPaths.includes(".github/skills/truthmark-portal/SKILL.md"),
).toBe(false);
expect(
oneHostPaths.includes(".github/prompts/truthmark-portal.prompt.md"),
).toBe(false);
const portalDisabled = createDefaultConfig();
portalDisabled.platforms = ["codex"];
portalDisabled.truthmark.generated.portal.enabled = false;
const disabledPaths = renderGeneratedSurfaces(portalDisabled).map(
(surface) => surface.path,
);
expect(disabledPaths.includes("AGENTS.md")).toBe(true);
expect(disabledPaths.includes(".agents/skills/truthmark-portal/SKILL.md")).toBe(
false,
);
expect(disabledPaths.includes(".agents/skills/truthmark-portal/SKILL.md")).toBe(
false,
);
});
});