mirror of
https://github.com/merlinhu1/truthmark.git
synced 2026-08-25 07:53:25 +02:00
feat: add Truthmark Portal workflow surfaces (#8)
* fix: harden workflow helper manifests and reports * docs: add helper script portability design * fix: reject failed helper statuses in completed reports * fix: align helper report sections metadata * fix: preserve sync helper statuses in parser * test: harden helper validator negatives * feat: add Copilot and Gemini Truthmark surfaces - generate Copilot and Gemini workflow skill packages with helper manifests - add Gemini subagent surfaces and validate helper CLI plumbing - update README/docs for helper-package support Verification: npm run check * docs: simplify README workflow surface overview Replace long generated file lists with conceptual layers and Mermaid architecture diagrams across localized READMEs. Verification: node dist/main.js check --json; npm run package:check * docs: show agent CLI feedback loop in README diagram Clarify that host-native agent workflows can call the installed Truthmark CLI for validation, indexing, and helper checks. Verification: node dist/main.js check --json; npm run package:check * fix: wrap validate JSON output in command envelope Return helper validation results under data.validation for --json output while preserving the existing human-readable validate output. Verification: npm run check; truthmark check/index JSON diagnostics. * fix: clarify helper validation status reporting Add explicit helper-status policy to Truth Sync and Truth Document surfaces so standalone Copilot prompts and Gemini commands only report ran/passed after the installed CLI validator succeeds. Update README and workflow docs to describe the shared installed-CLI validator contract instead of saying standalone surfaces mark helper packages unavailable. Verification: npm run check; npm run package:check; truthmark check/index JSON diagnostics. * fix: normalize helper status ids during validation Parse helper status entries with the same regex used for syntax validation and compare captured helper IDs against required helpers. Adds regressions for extra whitespace after helper bullets in Truth Sync and Truth Document reports. Verification: npm run check; npm run package:check; truthmark check/index JSON diagnostics. * fix: parse write lease YAML structurally * chore: bump version to 1.5.0 * docs: include validate in repo CLI boundary * fix: enforce workflow report validator contracts * fix: require manual review files in blocked sync reports * feat: add Truthmark Portal workflow surfaces - add Portal workflow guidance, config/defaults, and generated provider surfaces - cover Portal config path boundaries and generated surface contracts - clarify project-local policy docs are optional and config-driven --------- Co-authored-by: MerlinH <merlinh221@gmail.com>
This commit is contained in:
@@ -65,16 +65,16 @@ describe("renderTruthSyncInstructions", () => {
|
||||
|
||||
const instructions = renderTruthSyncInstructions(config);
|
||||
|
||||
expect(instructions).toContain("May write truth docs and docs/routes/index.md only");
|
||||
expect(instructions).toContain("Read docs/routes/index.md and only relevant child route files under docs/routes/areas/");
|
||||
expect(instructions).not.toContain("May write truth docs and docs/truthmark/areas.md only");
|
||||
expect(instructions).toContain("docs/routes/index.md; docs/routes/areas/");
|
||||
expect(instructions).toContain("only when present");
|
||||
expect(instructions).not.toContain("docs/truthmark/areas.md; docs/truthmark/areas/");
|
||||
});
|
||||
});
|
||||
|
||||
describe("agent-native workflow instructions", () => {
|
||||
it("renders Truth Structure and Truth Check summaries", () => {
|
||||
expect(renderTruthStructureInstructions()).toContain("truthmark-structure");
|
||||
expect(renderTruthStructureInstructions()).toContain("docs/truthmark/areas.md");
|
||||
expect(renderTruthStructureInstructions()).toContain("configured route files when present");
|
||||
expect(renderTruthStructureInstructions()).toContain("missing, stale, broad, overloaded, catch-all, unrouteable");
|
||||
expect(renderTruthStructureInstructions()).toContain("canonical current-truth destinations");
|
||||
expect(renderTruthStructureInstructions()).toContain("topology pressure");
|
||||
|
||||
@@ -11,7 +11,7 @@ describe("renderTruthRealizePrompt", () => {
|
||||
expect(prompt).toContain("### Manual Truth Realize");
|
||||
expect(prompt).toContain("Only run when the user explicitly asks");
|
||||
expect(prompt).toContain(
|
||||
"read the updated truth docs plus .truthmark/config.yml, route files, relevant code, and tests",
|
||||
"read the updated truth docs plus any present Truthmark config, route files, relevant code, and tests",
|
||||
);
|
||||
expect(prompt).toContain("Truth-doc ownership gate");
|
||||
expect(prompt).toContain(
|
||||
|
||||
@@ -44,7 +44,7 @@ describe("renderTruthCheckSkillBody", () => {
|
||||
);
|
||||
expect(skill).toContain("audit repository truth health");
|
||||
expect(skill).toContain(
|
||||
"Repository instruction docs such as docs/ai/repo-rules.md remain instruction authority.",
|
||||
"Repository instruction files and explicitly configured policy docs remain instruction authority when present; do not assume a repository uses any particular policy path.",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"Implementation code and canonical truth docs are inspected evidence for current behavior; they do not silently override workflow write boundaries.",
|
||||
@@ -60,7 +60,7 @@ describe("renderTruthCheckSkillBody", () => {
|
||||
expect(skill).toContain(
|
||||
"remove unsupported findings or mark open questions",
|
||||
);
|
||||
expect(skill).toContain("Truthmark hierarchy:");
|
||||
expect(skill).toContain("Truthmark hierarchy hints:");
|
||||
expect(skill).toContain("Product Decisions");
|
||||
expect(skill).toContain("Rationale");
|
||||
expect(skill).toContain("Truth Check: completed");
|
||||
|
||||
@@ -88,7 +88,7 @@ describe("renderTruthDocumentSkillBody", () => {
|
||||
"block and recommend Truth Structure when routing repair is unsafe, ambiguous, or outside the task boundary",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"Repository instruction docs such as docs/ai/repo-rules.md remain instruction authority.",
|
||||
"Repository instruction files and explicitly configured policy docs remain instruction authority when present; do not assume a repository uses any particular policy path.",
|
||||
);
|
||||
expect(skill).toContain("RepoIndex, RouteMap, ImpactSet, and ContextPack");
|
||||
expect(skill).toContain("repository-intelligence artifacts were not generated");
|
||||
|
||||
@@ -82,7 +82,7 @@ describe("renderTruthStructureSkillBody", () => {
|
||||
expect(skill).toContain(`truthmark-version: ${TRUTHMARK_VERSION}`);
|
||||
expect(skill).toContain("inspect repository layout");
|
||||
expect(skill).toContain(
|
||||
"Repository instruction docs such as docs/ai/repo-rules.md remain instruction authority.",
|
||||
"Repository instruction files and explicitly configured policy docs remain instruction authority when present; do not assume a repository uses any particular policy path.",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"Implementation code and canonical truth docs are inspected evidence for current behavior; they do not silently override workflow write boundaries.",
|
||||
@@ -95,7 +95,7 @@ describe("renderTruthStructureSkillBody", () => {
|
||||
expect(skill).toContain("docs/truth/**");
|
||||
expect(skill).toContain("docs/architecture/**");
|
||||
expect(skill).toContain("canonical current-truth destinations");
|
||||
expect(skill).toContain("Truthmark hierarchy:");
|
||||
expect(skill).toContain("Truthmark hierarchy hints:");
|
||||
expect(skill).toContain("Product Decisions");
|
||||
expect(skill).toContain("Rationale");
|
||||
expect(skill).toContain(
|
||||
|
||||
@@ -29,7 +29,7 @@ describe("renderTruthSyncWorkerPrompt", () => {
|
||||
);
|
||||
expect(prompt).toContain(".truthmark/config.yml");
|
||||
expect(prompt).toContain("Code verification is parent-owned");
|
||||
expect(prompt).toContain("docs/truthmark/areas.md");
|
||||
expect(prompt).toContain("configured route files");
|
||||
expect(prompt).toContain("status: completed | blocked");
|
||||
expect(prompt).toContain("filesChanged");
|
||||
expect(prompt).toContain("changedCodeReviewed");
|
||||
@@ -66,7 +66,7 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
"direct checkout inspection is the canonical path",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"Repository instruction docs such as docs/ai/repo-rules.md remain instruction authority.",
|
||||
"Repository instruction files and explicitly configured policy docs remain instruction authority when present; do not assume a repository uses any particular policy path.",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"Implementation code and canonical truth docs are inspected evidence for current behavior; they do not silently override workflow write boundaries.",
|
||||
@@ -85,9 +85,9 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
"verify only truth docs and leased truth routing files changed",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"Read .truthmark/config.yml, the configured root route index",
|
||||
"Inspect .truthmark/config.yml and configured route files only when they exist",
|
||||
);
|
||||
expect(skillBody).toContain("relevant child route files");
|
||||
expect(skillBody).toContain("configured route files");
|
||||
expect(skillBody).toContain("Topology quality gate");
|
||||
expect(skillBody).toContain(
|
||||
"missing, stale, broad, overloaded, catch-all route only",
|
||||
|
||||
@@ -11,7 +11,10 @@ import { renderTruthDocumentSkillBody } from "../../src/agents/truth-document.js
|
||||
import { renderTruthPreviewSkillBody } from "../../src/agents/truth-preview.js";
|
||||
import { renderTruthStructureSkillBody } from "../../src/agents/truth-structure.js";
|
||||
import { renderTruthSyncSkillBody } from "../../src/agents/truth-sync.js";
|
||||
import { renderTruthmarkRealizeSkill } from "../../src/templates/workflow-surfaces.js";
|
||||
import {
|
||||
renderTruthmarkPortalSkill,
|
||||
renderTruthmarkRealizeSkill,
|
||||
} from "../../src/templates/workflow-surfaces.js";
|
||||
|
||||
const renderWorkflowSkill = (id: (typeof TRUTHMARK_WORKFLOW_IDS)[number]) => {
|
||||
switch (id) {
|
||||
@@ -27,6 +30,8 @@ const renderWorkflowSkill = (id: (typeof TRUTHMARK_WORKFLOW_IDS)[number]) => {
|
||||
return renderTruthmarkRealizeSkill();
|
||||
case "truthmark-check":
|
||||
return renderTruthCheckSkillBody();
|
||||
case "truthmark-portal":
|
||||
return renderTruthmarkPortalSkill();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,6 +153,40 @@ describe("Truthmark workflow manifest", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("defines Truthmark Portal as a manual-only presentation workflow", () => {
|
||||
const workflow = getTruthmarkWorkflow("truthmark-portal");
|
||||
|
||||
expect(workflow.displayName).toBe("Truthmark Portal");
|
||||
expect(workflow.allowImplicitInvocation).toBe(false);
|
||||
expect(workflow.positiveTriggers).toEqual(
|
||||
expect.arrayContaining([
|
||||
"generate the Truthmark Portal",
|
||||
"refresh the committed HTML docs site",
|
||||
"update docs/truthmark-portal",
|
||||
]),
|
||||
);
|
||||
expect(workflow.negativeTriggers).toEqual(
|
||||
expect.arrayContaining([
|
||||
"code change sync",
|
||||
"route ownership repair",
|
||||
"truth validation or checking",
|
||||
"machine-readable agent context",
|
||||
]),
|
||||
);
|
||||
expect(workflow.allowedWrites).toEqual(["configured Portal output directory only"]);
|
||||
expect(workflow.reportSections).toEqual([
|
||||
"Output path",
|
||||
"Page count",
|
||||
"Diagrams/assets",
|
||||
"Source docs reviewed",
|
||||
"Skipped/ambiguous docs",
|
||||
"Validation",
|
||||
"Markdown canonical statement",
|
||||
]);
|
||||
expect(workflow.subagents).toBeUndefined();
|
||||
expect(workflow.writeSubagents).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps routing descriptions focused on trigger selection", () => {
|
||||
for (const id of TRUTHMARK_WORKFLOW_IDS) {
|
||||
const { description } = getTruthmarkWorkflow(id);
|
||||
|
||||
@@ -4,6 +4,19 @@ import { createTempRepo } from "../helpers/temp-repo.js";
|
||||
import { loadConfig } from "../../src/config/load.js";
|
||||
|
||||
describe("loadConfig", () => {
|
||||
const writeConfig = async (
|
||||
repo: Awaited<ReturnType<typeof createTempRepo>>,
|
||||
extraConfig = "",
|
||||
) => {
|
||||
await repo.writeFile(
|
||||
".truthmark/config.yml",
|
||||
`version: 1
|
||||
authority:
|
||||
- docs/truthmark/areas.md
|
||||
${extraConfig}`,
|
||||
);
|
||||
};
|
||||
|
||||
it("loads a valid config and applies defaults for optional frontmatter and ignore fields", async () => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
@@ -289,6 +302,143 @@ outputs:
|
||||
}
|
||||
});
|
||||
|
||||
it("defaults omitted Portal config", async () => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
try {
|
||||
await writeConfig(repo);
|
||||
|
||||
const result = await loadConfig(repo.rootDir);
|
||||
|
||||
expect(result.status).toBe("loaded");
|
||||
expect(result.diagnostics).toEqual([]);
|
||||
expect(result.config?.truthmarkPortal).toEqual({
|
||||
enabled: false,
|
||||
output: "docs/truthmark-portal",
|
||||
template: "default",
|
||||
});
|
||||
} finally {
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("loads enabled Portal config", async () => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
try {
|
||||
await writeConfig(
|
||||
repo,
|
||||
`truthmark-portal:
|
||||
enabled: true
|
||||
output: docs/portal
|
||||
template: docs/templates/portal.md
|
||||
`,
|
||||
);
|
||||
|
||||
const result = await loadConfig(repo.rootDir);
|
||||
|
||||
expect(result.status).toBe("loaded");
|
||||
expect(result.diagnostics).toEqual([]);
|
||||
expect(result.config?.truthmarkPortal).toEqual({
|
||||
enabled: true,
|
||||
output: "docs/portal",
|
||||
template: "docs/templates/portal.md",
|
||||
});
|
||||
} finally {
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("defaults omitted Portal enabled when nested Portal config is present", async () => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
try {
|
||||
await writeConfig(
|
||||
repo,
|
||||
`truthmark-portal:
|
||||
output: docs/portal
|
||||
`,
|
||||
);
|
||||
|
||||
const result = await loadConfig(repo.rootDir);
|
||||
|
||||
expect(result.status).toBe("loaded");
|
||||
expect(result.config?.truthmarkPortal).toEqual({
|
||||
enabled: false,
|
||||
output: "docs/portal",
|
||||
template: "default",
|
||||
});
|
||||
} finally {
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
["/tmp/truthmark-portal", "absolute output path"],
|
||||
["../truthmark-portal", "parent output traversal"],
|
||||
["docs/truth", "canonical truth root overlap"],
|
||||
["docs/truthmark/areas", "routing files root overlap"],
|
||||
["AGENTS.md/portal", "instruction target overlap"],
|
||||
])("rejects unsafe Portal output for %s (%s)", async (output) => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
try {
|
||||
await writeConfig(
|
||||
repo,
|
||||
`truthmark-portal:
|
||||
enabled: true
|
||||
output: ${output}
|
||||
`,
|
||||
);
|
||||
|
||||
const result = await loadConfig(repo.rootDir);
|
||||
|
||||
expect(result.status).toBe("invalid");
|
||||
expect(result.config).toBeNull();
|
||||
expect(result.diagnostics).toEqual([
|
||||
expect.objectContaining({
|
||||
category: "config",
|
||||
severity: "error",
|
||||
message: expect.stringContaining("truthmark-portal.output"),
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
["/tmp/portal-template.md", "absolute template path"],
|
||||
["docs/templates/../portal.md", "parent template traversal"],
|
||||
])("rejects unsafe Portal template for %s (%s)", async (template) => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
try {
|
||||
await writeConfig(
|
||||
repo,
|
||||
`truthmark-portal:
|
||||
enabled: true
|
||||
output: docs/portal
|
||||
template: ${template}
|
||||
`,
|
||||
);
|
||||
|
||||
const result = await loadConfig(repo.rootDir);
|
||||
|
||||
expect(result.status).toBe("invalid");
|
||||
expect(result.config).toBeNull();
|
||||
expect(result.diagnostics).toEqual([
|
||||
expect.objectContaining({
|
||||
category: "config",
|
||||
severity: "error",
|
||||
message: expect.stringContaining("truthmark-portal.template"),
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("returns a typed missing status when config does not exist yet", async () => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
|
||||
@@ -141,6 +141,19 @@ export const WORKFLOW_ROUTING_EVAL_CASES: WorkflowRoutingEvalCase[] = [
|
||||
forbiddenWorkflows: ["truthmark-sync", "truthmark-check"],
|
||||
source: "manifest-positive",
|
||||
},
|
||||
{
|
||||
id: "portal-explicit-refresh",
|
||||
userPrompt:
|
||||
"Refresh the committed Truthmark Portal static HTML docs site under docs/truthmark-portal.",
|
||||
expectedWorkflow: "truthmark-portal",
|
||||
expectedReason: "generate or refresh the committed static HTML Portal",
|
||||
expectedManifestSignals: [
|
||||
"generate, refresh, or update the Truthmark Portal static HTML site",
|
||||
"update docs/truthmark-portal",
|
||||
],
|
||||
forbiddenWorkflows: ["truthmark-sync", "truthmark-check"],
|
||||
source: "manifest-positive",
|
||||
},
|
||||
{
|
||||
id: "adjacent-sync-not-realize",
|
||||
userPrompt:
|
||||
|
||||
@@ -16,6 +16,7 @@ const WORKFLOW_COMMAND_PATHS: Record<TruthmarkWorkflowId, string> = {
|
||||
"truthmark-preview": ".gemini/commands/truthmark/preview.toml",
|
||||
"truthmark-realize": ".gemini/commands/truthmark/realize.toml",
|
||||
"truthmark-check": ".gemini/commands/truthmark/check.toml",
|
||||
"truthmark-portal": ".gemini/commands/truthmark/portal.toml",
|
||||
};
|
||||
|
||||
const WORKFLOW_SURFACE_PATHS = (id: TruthmarkWorkflowId): readonly string[] => [
|
||||
@@ -108,6 +109,14 @@ const SURFACE_CONTRACT_TERMS: Record<TruthmarkWorkflowId, readonly string[]> = {
|
||||
"Files reviewed:",
|
||||
"Validation:",
|
||||
],
|
||||
"truthmark-portal": [
|
||||
"manual-only presentation workflow",
|
||||
"configured Portal output directory only",
|
||||
"Markdown remains canonical",
|
||||
"Truthmark Portal: completed",
|
||||
"Output path:",
|
||||
"Markdown canonical statement:",
|
||||
],
|
||||
};
|
||||
|
||||
const manifestRoutingText = (id: TruthmarkWorkflowId): string => {
|
||||
@@ -136,6 +145,7 @@ const candidateManifestText = (
|
||||
|
||||
const buildGeneratedSurfaceMap = (): Map<string, string> => {
|
||||
const config = createDefaultConfig();
|
||||
config.truthmarkPortal.enabled = true;
|
||||
return new Map(
|
||||
renderGeneratedSurfaces(config).map((surface) => [
|
||||
surface.path,
|
||||
|
||||
+19
-7
@@ -331,7 +331,7 @@ describe("runInit", () => {
|
||||
"Explicit workflows: Truth Structure, Truth Document, Truth Preview, Truth Realize, Truth Check",
|
||||
);
|
||||
expect(agents).toContain("load the installed skill for details");
|
||||
expect(agents).toContain("Hierarchy: config .truthmark/config.yml");
|
||||
expect(agents).toContain("Hierarchy hints: config .truthmark/config.yml");
|
||||
expect(agents).toContain("routes docs/truthmark/areas.md");
|
||||
expect(agents).toContain("docs/truthmark/areas/**/*.md");
|
||||
expect(agents).toContain("Truth docs: docs/truth/**/*.md");
|
||||
@@ -351,7 +351,7 @@ describe("runInit", () => {
|
||||
expect(agents).toContain("code changed -> tests -> Sync -> report");
|
||||
expect(agents).not.toContain("Truth Sync: completed");
|
||||
expect(agents).not.toContain("Truth Realize: completed");
|
||||
expect(agents.match(/Hierarchy: config/g)).toHaveLength(1);
|
||||
expect(agents.match(/Hierarchy hints: config/g)).toHaveLength(1);
|
||||
expect(agents.match(/Decisions live/g)).toHaveLength(1);
|
||||
expect(structureSkill).toContain("name: truthmark-structure");
|
||||
expect(structureSkill).toContain("support/procedure.md");
|
||||
@@ -430,9 +430,9 @@ describe("runInit", () => {
|
||||
expect(syncReportTemplate).toContain("Truth Sync: completed");
|
||||
expect(syncReportTemplate).toContain("Truth Sync: blocked");
|
||||
expect(syncSkill).toContain(
|
||||
"Read .truthmark/config.yml, the configured root route index",
|
||||
"Inspect .truthmark/config.yml and configured route files",
|
||||
);
|
||||
expect(syncSkill).toContain("relevant child route files");
|
||||
expect(syncSkill).toContain("then inspect relevant canonical docs directly");
|
||||
expect(syncSkill).not.toContain(".truthmark/local.yml");
|
||||
expect(syncSkill).not.toContain("truth_sync.sync_agent");
|
||||
expect(syncSkill).not.toContain(
|
||||
@@ -657,6 +657,18 @@ describe("runInit", () => {
|
||||
diagnostic.file === ".opencode/agents/truth-route-auditor.md",
|
||||
),
|
||||
).toBe(true);
|
||||
const diagnosticCategoriesByFile = new Map(
|
||||
result.diagnostics.map((diagnostic) => [diagnostic.file, diagnostic.category]),
|
||||
);
|
||||
for (const file of [
|
||||
".github/prompts/truthmark-realize.prompt.md",
|
||||
".github/skills/truthmark-realize/SKILL.md",
|
||||
".claude/skills/truthmark-realize/SKILL.md",
|
||||
".opencode/skills/truthmark-realize/SKILL.md",
|
||||
".gemini/skills/truthmark-realize/SKILL.md",
|
||||
]) {
|
||||
expect(diagnosticCategoriesByFile.get(file)).toBe("realization");
|
||||
}
|
||||
expect(
|
||||
result.diagnostics.some((diagnostic) =>
|
||||
diagnostic.message.includes("Created"),
|
||||
@@ -856,14 +868,14 @@ Agent-specific:
|
||||
const geminiInstructions = await repo.readFile("GEMINI.md");
|
||||
expect(geminiInstructions).not.toContain("/truthmark:sync");
|
||||
expect(geminiInstructions).toContain(
|
||||
"Use that file as the primary repository instruction source for this agent.",
|
||||
"Use explicitly configured repository policy docs only when they exist in this checkout.",
|
||||
);
|
||||
expect(geminiInstructions).toContain("Agent-specific:");
|
||||
expect(geminiInstructions).toContain(
|
||||
"Read `docs/README.md` only when choosing or updating canonical docs.",
|
||||
"Read the configured Truthmark routing files when choosing or updating canonical docs.",
|
||||
);
|
||||
expect(geminiInstructions).toContain(
|
||||
"Use `docs/ai/agent-onboarding.md` only when task routing is unclear or cross-area.",
|
||||
"Use repository onboarding or docs-map files only when present and needed for unclear or cross-area routing.",
|
||||
);
|
||||
expect(geminiInstructions).not.toContain("for Codex");
|
||||
expect(geminiInstructions).not.toContain("Codex-specific");
|
||||
|
||||
@@ -78,10 +78,14 @@ describe("installed workflow contract", () => {
|
||||
expect(agents).not.toContain("Truth Sync: completed");
|
||||
expect(agents).not.toContain("Truth Realize: completed");
|
||||
expect(structureSkill).toContain("name: truthmark-structure");
|
||||
expect(structureSkill).toContain("docs/ai/repo-rules.md");
|
||||
expect(structureSkill).toContain(
|
||||
"Follow repository instruction files that exist in this checkout",
|
||||
);
|
||||
expect(structureSkill).toContain("docs/truthmark/areas.md");
|
||||
expect(syncSkill).toContain("name: truthmark-sync");
|
||||
expect(syncSkill).toContain("docs/ai/repo-rules.md");
|
||||
expect(syncSkill).toContain(
|
||||
"Follow repository instruction files that exist in this checkout",
|
||||
);
|
||||
expect(syncSkill).toContain(
|
||||
"Use this skill automatically before finishing",
|
||||
);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createDefaultConfig } from "../../src/config/defaults.js";
|
||||
import { renderAgentsBlock } from "../../src/templates/agents-block.js";
|
||||
import { renderGeneratedSurfaces } from "../../src/templates/generated-surfaces.js";
|
||||
|
||||
const portalPaths = [
|
||||
".codex/skills/truthmark-portal/SKILL.md",
|
||||
".codex/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",
|
||||
".gemini/skills/truthmark-portal/SKILL.md",
|
||||
".gemini/commands/truthmark/portal.toml",
|
||||
];
|
||||
|
||||
describe("Truthmark Portal generated surfaces", () => {
|
||||
it("omits Portal surfaces and AGENTS wording when disabled", () => {
|
||||
const config = createDefaultConfig();
|
||||
const paths = renderGeneratedSurfaces(config).map((surface) => surface.path);
|
||||
|
||||
expect(config.truthmarkPortal.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.truthmarkPortal = {
|
||||
enabled: true,
|
||||
output: "docs/project-map",
|
||||
template: "docs/truthmark/portal-templates/product.md",
|
||||
};
|
||||
|
||||
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(".codex/skills/truthmark-portal/SKILL.md") ?? "";
|
||||
const portalProcedure =
|
||||
byPath.get(".codex/skills/truthmark-portal/support/procedure.md") ?? "";
|
||||
const copilotPrompt = byPath.get(".github/prompts/truthmark-portal.prompt.md") ?? "";
|
||||
const geminiCommand = byPath.get(".gemini/commands/truthmark/portal.toml") ?? "";
|
||||
const agentsBlock = renderAgentsBlock(config);
|
||||
|
||||
for (const text of [portalSkill, portalProcedure, copilotPrompt, geminiCommand]) {
|
||||
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-portal");
|
||||
expect(text).toContain("docs/project-map");
|
||||
expect(text).toContain("docs/truthmark/portal-templates/product.md");
|
||||
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(portalProcedure).toContain("replace the entire output directory");
|
||||
expect(portalProcedure).toContain("configured Portal output directory only");
|
||||
expect(portalProcedure).toContain("explicit user or template request");
|
||||
expect(agentsBlock).toContain("Truthmark Portal is a separate manual-only presentation workflow");
|
||||
expect(agentsBlock).toContain("default `docs/truthmark-portal/`");
|
||||
expect(agentsBlock).toContain("Markdown remains canonical");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user