docs: align Truthmark workflow docs and skill guidance

This commit is contained in:
MerlinH
2026-05-10 13:18:26 +10:00
parent a87e303f37
commit 9a691dd84f
47 changed files with 622 additions and 477 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ describe("renderTruthSyncInstructions", () => {
expect(instructions).toContain("Automatic finish-time trigger");
expect(instructions).toContain("use the truthmark-sync skill before finishing");
expect(instructions).toContain(
"OpenCode /skill truthmark-sync; Codex /truthmark-sync or $truthmark-sync; Gemini CLI /truthmark:sync",
"OpenCode /skill truthmark-sync; Codex /truthmark-sync or $truthmark-sync; Claude Code /truthmark-sync; GitHub Copilot /truthmark-sync; Gemini CLI /truthmark:sync",
);
expect(instructions).toContain("staged, unstaged, and untracked functional code files");
expect(instructions).toContain("Run relevant tests before finishing");
+70
View File
@@ -658,6 +658,76 @@ Update truth when:
}
});
it("reports stale Claude Code generated skill surfaces", async () => {
const repo = await createTempRepo();
try {
await initializeRepo(repo.rootDir);
await repo.writeFile(
".claude/skills/truthmark-sync/SKILL.md",
`${await repo.readFile(".claude/skills/truthmark-sync/SKILL.md")}\nmanual drift\n`,
);
const result = await runCheck(repo.rootDir);
expect(result.diagnostics).toEqual(
expect.arrayContaining([
expect.objectContaining({
category: "generated-surface",
severity: "review",
file: ".claude/skills/truthmark-sync/SKILL.md",
message: expect.stringContaining("stale"),
}),
]),
);
} finally {
await repo.cleanup();
}
});
it("reports stale GitHub Copilot generated prompt surfaces", async () => {
const repo = await createTempRepo();
try {
await repo.writeFile(
".truthmark/config.yml",
`version: 1
platforms:
- github-copilot
authority:
- TRUTHMARK.md
- docs/truthmark/areas.md
frontmatter:
required: []
recommended: []
ignore: []
realization:
enabled: true
`,
);
await initializeRepo(repo.rootDir);
await repo.writeFile(
".github/prompts/truthmark-sync.prompt.md",
`${await repo.readFile(".github/prompts/truthmark-sync.prompt.md")}\nmanual drift\n`,
);
const result = await runCheck(repo.rootDir);
expect(result.diagnostics).toEqual(
expect.arrayContaining([
expect.objectContaining({
category: "generated-surface",
severity: "review",
file: ".github/prompts/truthmark-sync.prompt.md",
message: expect.stringContaining("stale"),
}),
]),
);
} finally {
await repo.cleanup();
}
});
it("ignores manual version notes outside managed instruction blocks", async () => {
const repo = await createTempRepo();
+2
View File
@@ -18,6 +18,8 @@ describe("runConfig", () => {
"area_files_root: docs/truthmark/areas",
);
const config = await repo.readFile(".truthmark/config.yml");
expect(config).toContain(" - github-copilot");
expect(config).toContain(" - gemini-cli");
expect(config).toContain("features: docs/features");
expect(config).toContain("docs/features/**/*.md");
expect(config).not.toContain("features_current");
+3 -3
View File
@@ -24,7 +24,7 @@ realization:
expect(result.diagnostics).toEqual([]);
expect(result.config).toMatchObject({
version: 1,
platforms: ["codex", "opencode", "claude-code"],
platforms: ["codex", "opencode", "claude-code", "github-copilot", "gemini-cli"],
authority: ["TRUTHMARK.md"],
docs: {
layout: "hierarchical",
@@ -64,7 +64,7 @@ instruction_targets:
- AGENTS.md
platforms:
- codex
- cursor
- github-copilot
frontmatter:
required: []
recommended:
@@ -79,7 +79,7 @@ realization:
const result = await loadConfig(repo.rootDir);
expect(result.diagnostics).toEqual([]);
expect(result.config?.platforms).toEqual(["codex", "cursor"]);
expect(result.config?.platforms).toEqual(["codex", "github-copilot"]);
expect(result.config?.instructionTargets).toEqual(["AGENTS.md"]);
expect(result.config?.frontmatter.recommended).toEqual(["status"]);
expect(result.config?.ignore).toEqual(["dist/**"]);
+6 -1
View File
@@ -100,11 +100,16 @@ describe("classifyPath", () => {
expect(classifyPath("vendor/lib/index.rb", ["vendor/**"])).toBe("ignored");
expect(classifyPath(".truthmark/cache/state.json", [])).toBe("derived");
expect(classifyPath(".truthmark/sync/report.md", [])).toBe("derived");
expect(classifyPath(".claude/skills/truthmark-sync/SKILL.md", [])).toBe(
"derived",
);
expect(classifyPath(".codex/skills/truthmark-sync/SKILL.md", [])).toBe("derived");
expect(classifyPath(".opencode/skills/truthmark-sync/SKILL.md", [])).toBe("derived");
expect(classifyPath("skills/truthmark-sync/SKILL.md", [])).toBe("markdown");
expect(classifyPath(".cursor/rules/truthmark.mdc", [])).toBe("derived");
expect(classifyPath(".github/copilot-instructions.md", [])).toBe("derived");
expect(classifyPath(".github/prompts/truthmark-sync.prompt.md", [])).toBe(
"derived",
);
expect(classifyPath("CLAUDE.md", [])).toBe("derived");
expect(classifyPath("GEMINI.md", [])).toBe("derived");
expect(classifyPath(".gemini/commands/truthmark/sync.toml", [])).toBe("derived");
+14 -10
View File
@@ -19,13 +19,19 @@ describe("runInit instruction integration", () => {
expect(agents.match(/<!-- truthmark:start -->/g)).toHaveLength(1);
expect(agents.match(/<!-- truthmark:end -->/g)).toHaveLength(1);
expect(agents.split("\n").length).toBeLessThanOrEqual(65);
expect(agents.split("\n").length).toBeLessThanOrEqual(25);
expect(agents.slice(0, 220)).toContain("Truthmark Workflow");
expect(agents).toContain("/skill truthmark-structure");
expect(agents).toContain("/skill truthmark-sync");
expect(agents).toContain("/skill truthmark-check");
expect(agents).toContain(`Generated by Truthmark ${TRUTHMARK_VERSION}`);
expect(agents).toContain("Automatic finish-time trigger");
expect(agents).toContain("Sync: finish-time when functional code changed");
expect(agents).toContain("code changed -> tests -> Sync -> report");
expect(agents).toContain("Delegation is host-owned");
expect(agents).toContain(
"Explicit workflows: Truth Structure, Truth Realize, Truth Check",
);
expect(agents).toContain("load the installed skill for details");
expect(agents).not.toContain("/skill truthmark-structure");
expect(agents).not.toContain("/skill truthmark-check");
expect(agents).not.toContain(
"truthmark check --json --workflow truth-sync",
);
@@ -33,8 +39,8 @@ describe("runInit instruction integration", () => {
expect(agents).not.toContain("Truth Sync: completed");
expect(agents).not.toContain("Truth Sync: skipped");
expect(agents).not.toContain("Truth Realize: completed");
expect(agents).toContain("Truth Check");
expect(agents).toContain("Inspect the current checkout directly");
expect(agents).not.toContain("### Truth Check");
expect(agents).toContain("inspect checkout directly");
expect(agents).not.toContain("truthmark packet --changed");
await expect(fs.stat(`${repo.rootDir}/OPENCODE.md`)).rejects.toThrow();
@@ -73,12 +79,10 @@ realization:
const agents = await repo.readFile("AGENTS.md");
expect(agents.match(/<!-- truthmark:start -->/g)).toHaveLength(1);
expect(agents).toContain("host supports subagent dispatch");
expect(agents).toContain("Delegation is host-owned");
expect(agents).not.toContain(".truthmark/local.yml");
expect(agents).not.toContain("truth_sync.sync_agent");
expect(agents).toContain(
"Later functional-code changes reopen the finish-time requirement",
);
expect(agents).toContain("later functional changes reopen the gate");
} finally {
await repo.cleanup();
}
+130 -14
View File
@@ -143,17 +143,24 @@ describe("runInit", () => {
".opencode/skills/truthmark-check/SKILL.md",
);
const claudeInstructions = await repo.readFile("CLAUDE.md");
const syncClaudeSkill = await repo.readFile(
".claude/skills/truthmark-sync/SKILL.md",
);
expect(agents.match(/<!-- truthmark:start -->/g)).toHaveLength(1);
expect(claudeInstructions).toContain("Truthmark Workflow");
expect(claudeInstructions.split("\n").length).toBeLessThanOrEqual(65);
expect(agents).toContain("### Truth Structure");
expect(claudeInstructions.split("\n").length).toBeLessThanOrEqual(25);
expect(agents).not.toContain("### Truth Structure");
expect(agents).toContain(`Generated by Truthmark ${TRUTHMARK_VERSION}`);
expect(agents).toContain("Automatic finish-time trigger");
expect(agents).toContain("Sync: finish-time when functional code changed");
expect(agents).toContain("use the truthmark-sync skill before finishing");
expect(agents).toContain("/skill truthmark-sync");
expect(agents).toContain("/skill truthmark-structure");
expect(agents).toContain("/skill truthmark-check");
expect(agents).not.toContain("/skill truthmark-structure");
expect(agents).not.toContain("/skill truthmark-check");
expect(agents).toContain(
"Explicit workflows: Truth Structure, Truth Realize, Truth Check",
);
expect(agents).toContain("load the installed skill for details");
expect(agents).toContain("Truthmark hierarchy:");
expect(agents).toContain("Root route index: docs/truthmark/areas.md");
expect(agents).toContain(
@@ -166,15 +173,14 @@ describe("runInit", () => {
expect(agents).not.toContain(
"truthmark check --json --workflow truth-sync",
);
expect(agents).toContain("### Manual Truth Realize");
expect(agents).toContain("### Truth Check");
expect(agents).toContain("Only run when the user explicitly asks");
expect(agents).toContain("host supports subagent dispatch");
expect(agents).not.toContain("### Manual Truth Realize");
expect(agents).not.toContain("### Truth Check");
expect(agents).toContain("Delegation is host-owned");
expect(agents).not.toContain(".truthmark/local.yml");
expect(agents).not.toContain("truth_sync.sync_agent");
expect(agents).toContain("must not rewrite functional code");
expect(agents).toContain("do not edit truth docs or truth routing");
expect(agents).toContain("documentation-only change");
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(/Truthmark hierarchy:/g)).toHaveLength(1);
@@ -212,6 +218,10 @@ describe("runInit", () => {
expect(syncOpenCodeSkill).toContain(
"Use this skill automatically before finishing",
);
expect(syncClaudeSkill).toContain("name: truthmark-sync");
expect(syncClaudeSkill).toContain(
"Use this skill automatically before finishing",
);
expect(realizeSkill).toContain("name: truthmark-realize");
expect(realizeSkill).toContain("user-invocable: true");
expect(realizeSkill).toContain("may write functional code only");
@@ -257,6 +267,13 @@ describe("runInit", () => {
diagnostic.file === ".codex/skills/truthmark-sync/SKILL.md",
),
).toBe(true);
expect(
result.diagnostics.some(
(diagnostic) =>
diagnostic.category === "truth-sync" &&
diagnostic.file === ".claude/skills/truthmark-sync/SKILL.md",
),
).toBe(true);
expect(
result.diagnostics.some(
(diagnostic) =>
@@ -290,7 +307,6 @@ describe("runInit", () => {
`version: 1
platforms:
- codex
- cursor
- github-copilot
- gemini-cli
authority:
@@ -312,12 +328,15 @@ realization:
await expect(
fs.stat(`${repo.rootDir}/.codex/skills/truthmark-sync/SKILL.md`),
).resolves.toBeTruthy();
await expect(
fs.stat(`${repo.rootDir}/.cursor/rules/truthmark.mdc`),
).resolves.toBeTruthy();
await expect(
fs.stat(`${repo.rootDir}/.github/copilot-instructions.md`),
).resolves.toBeTruthy();
await expect(
fs.stat(`${repo.rootDir}/.github/prompts/truthmark-sync.prompt.md`),
).resolves.toBeTruthy();
expect(
await repo.readFile(".github/prompts/truthmark-sync.prompt.md"),
).toContain("GitHub Copilot /truthmark-sync");
await expect(fs.stat(`${repo.rootDir}/GEMINI.md`)).resolves.toBeTruthy();
await expect(
fs.stat(`${repo.rootDir}/.gemini/commands/truthmark/structure.toml`),
@@ -356,6 +375,103 @@ realization:
}
});
it("installs Claude Code project skills when only claude-code is configured", async () => {
const repo = await createTempRepo();
try {
await repo.writeFile(
".truthmark/config.yml",
`version: 1
platforms:
- claude-code
authority:
- TRUTHMARK.md
- docs/truthmark/areas.md
instruction_targets:
- AGENTS.md
frontmatter:
required: []
recommended: []
ignore: []
realization:
enabled: true
`,
);
await runInit(repo.rootDir);
expect(await repo.readFile("CLAUDE.md")).toContain("Truthmark Workflow");
expect(await repo.readFile("CLAUDE.md")).toContain(
"Claude Code /truthmark-sync",
);
expect(
await repo.readFile(".claude/skills/truthmark-sync/SKILL.md"),
).toContain("Claude Code /truthmark-sync");
expect(
await repo.readFile(".claude/skills/truthmark-structure/SKILL.md"),
).toContain("name: truthmark-structure");
expect(
await repo.readFile(".claude/skills/truthmark-check/SKILL.md"),
).toContain("name: truthmark-check");
expect(
await repo.readFile(".claude/skills/truthmark-realize/SKILL.md"),
).toContain("Claude Code /truthmark-realize");
await expect(
fs.stat(`${repo.rootDir}/.codex/skills/truthmark-sync/SKILL.md`),
).rejects.toThrow();
} finally {
await repo.cleanup();
}
});
it("installs GitHub Copilot prompt files when only github-copilot is configured", async () => {
const repo = await createTempRepo();
try {
await repo.writeFile(
".truthmark/config.yml",
`version: 1
platforms:
- github-copilot
authority:
- TRUTHMARK.md
- docs/truthmark/areas.md
instruction_targets:
- AGENTS.md
frontmatter:
required: []
recommended: []
ignore: []
realization:
enabled: true
`,
);
await runInit(repo.rootDir);
expect(await repo.readFile(".github/copilot-instructions.md")).toContain(
"Truthmark Workflow",
);
expect(
await repo.readFile(".github/prompts/truthmark-sync.prompt.md"),
).toContain("GitHub Copilot /truthmark-sync");
expect(
await repo.readFile(".github/prompts/truthmark-structure.prompt.md"),
).toContain("name: truthmark-structure");
expect(
await repo.readFile(".github/prompts/truthmark-check.prompt.md"),
).toContain("name: truthmark-check");
expect(
await repo.readFile(".github/prompts/truthmark-realize.prompt.md"),
).toContain("GitHub Copilot /truthmark-realize");
await expect(
fs.stat(`${repo.rootDir}/.codex/skills/truthmark-sync/SKILL.md`),
).rejects.toThrow();
} finally {
await repo.cleanup();
}
});
it("preserves existing docs and authored AGENTS content while scaffolding hierarchy", async () => {
const repo = await createTempRepo();
@@ -29,6 +29,9 @@ describe("installed workflow contract", () => {
const syncOpenCodeSkill = await repo.readFile(
".opencode/skills/truthmark-sync/SKILL.md",
);
const syncClaudeSkill = await repo.readFile(
".claude/skills/truthmark-sync/SKILL.md",
);
const realizeSkill = await repo.readFile(
".codex/skills/truthmark-realize/SKILL.md",
);
@@ -39,23 +42,23 @@ describe("installed workflow contract", () => {
".codex/skills/truthmark-check/SKILL.md",
);
expect(agents.split("\n").length).toBeLessThanOrEqual(65);
expect(agents).toContain("### Truth Structure");
expect(agents.split("\n").length).toBeLessThanOrEqual(25);
expect(agents).not.toContain("### Truth Structure");
expect(agents).toContain("### Truth Sync");
expect(agents).toContain("### Truth Check");
expect(agents).not.toContain("### Truth Check");
expect(agents).toContain(`Generated by Truthmark ${TRUTHMARK_VERSION}`);
expect(agents).toContain("use the truthmark-sync skill before finishing");
expect(agents).toContain("/skill truthmark-structure");
expect(agents).toContain("/skill truthmark-sync");
expect(agents).toContain("/skill truthmark-check");
expect(agents).not.toContain("/skill truthmark-structure");
expect(agents).not.toContain("/skill truthmark-check");
expect(agents).toContain(
"Explicit workflows: Truth Structure, Truth Realize, Truth Check",
);
expect(agents).not.toContain(
"truthmark check --json --workflow truth-sync",
);
expect(agents).toContain("Inspect the current checkout directly");
expect(agents).toContain("### Manual Truth Realize");
expect(agents).toContain(
"This is a manual installed instruction or skill, not a dedicated CLI command.",
);
expect(agents).toContain("inspect checkout directly");
expect(agents).not.toContain("### Manual Truth Realize");
expect(agents).not.toContain("Truth Sync: completed");
expect(agents).not.toContain("Truth Realize: completed");
expect(structureSkill).toContain("name: truthmark-structure");
@@ -72,6 +75,10 @@ describe("installed workflow contract", () => {
"direct checkout inspection is the canonical path",
);
expect(syncOpenCodeSkill).toContain("name: truthmark-sync");
expect(syncClaudeSkill).toContain("name: truthmark-sync");
expect(syncClaudeSkill).toContain(
"Use this skill automatically before finishing",
);
expect(realizeSkill).toContain("name: truthmark-realize");
expect(realizeSkill).toContain(
"Use this skill only when the user explicitly asks",
@@ -39,6 +39,9 @@ describe("init and check workflow acceptance", () => {
await expect(
fs.stat(`${repo.rootDir}/.codex/skills/truthmark-check/SKILL.md`),
).resolves.toBeTruthy();
await expect(
fs.stat(`${repo.rootDir}/.claude/skills/truthmark-sync/SKILL.md`),
).resolves.toBeTruthy();
const checkResult = await runCli(["check", "--json"], { cwd: repo.rootDir });
+33 -9
View File
@@ -50,8 +50,15 @@ describe("discoverMarkdownDocuments", () => {
".opencode/skills/truthmark-sync/SKILL.md",
"# Ignore me\n",
);
await repo.writeFile(".cursor/rules/truthmark.mdc", "# Ignore me\n");
await repo.writeFile(
".claude/skills/truthmark-sync/SKILL.md",
"# Ignore me\n",
);
await repo.writeFile(".github/copilot-instructions.md", "# Ignore me\n");
await repo.writeFile(
".github/prompts/truthmark-sync.prompt.md",
"# Ignore me\n",
);
await repo.writeFile("CLAUDE.md", "# Ignore me\n");
await repo.writeFile("GEMINI.md", "# Ignore me\n");
await repo.writeFile(
@@ -100,7 +107,13 @@ describe("init templates", () => {
expect(config).toMatchObject({
version: 1,
platforms: ["codex", "opencode", "claude-code"],
platforms: [
"codex",
"opencode",
"claude-code",
"github-copilot",
"gemini-cli",
],
authority: expect.any(Array),
instruction_targets: expect.any(Array),
frontmatter: expect.any(Object),
@@ -114,7 +127,7 @@ describe("init templates", () => {
expect(config.docs.roots).not.toHaveProperty("specs_draft");
});
it("renders TRUTHMARK.md with branch-local truth, automatic sync, and manual realize guidance", () => {
it("renders TRUTHMARK.md as a compact branch-local truth contract", () => {
const truthmark = renderTruthmarkTemplate();
expect(truthmark).toContain(
@@ -125,11 +138,14 @@ describe("init templates", () => {
);
expect(truthmark).toContain("rerun `truthmark init`");
expect(truthmark).toContain(
"Truth Sync runs automatically before finishing when functional code changes exist",
"Workflow runtime lives in installed skills and managed instruction blocks.",
);
expect(truthmark).toContain(
"Truth Realize is manual and updates code to match truth docs.",
"Truth Sync follows code; Truth Realize follows docs.",
);
expect(truthmark.split("\n").length).toBeLessThanOrEqual(18);
expect(truthmark).not.toContain("Truth Structure");
expect(truthmark).not.toContain("Truth Check");
});
it("seeds docs/truthmark/areas.md from discovered docs without moving them", () => {
@@ -158,11 +174,19 @@ describe("init templates", () => {
expect(agentsBlock).toContain("<!-- truthmark:start -->");
expect(agentsBlock).toContain("<!-- truthmark:end -->");
expect(agentsBlock).toContain("### Manual Truth Realize");
expect(agentsBlock).toContain("May write truth docs");
expect(agentsBlock.split("\n").length).toBeLessThanOrEqual(25);
expect(agentsBlock).not.toContain("### Manual Truth Realize");
expect(agentsBlock).not.toContain("### Truth Structure");
expect(agentsBlock).not.toContain("### Truth Check");
expect(agentsBlock).toContain(
"Explicit workflows: Truth Structure, Truth Realize, Truth Check",
);
expect(agentsBlock).toContain(
"may write truth docs and docs/truthmark/areas.md only",
);
expect(agentsBlock).toContain("must not rewrite functional code");
expect(agentsBlock).toContain("write functional code only");
expect(agentsBlock).toContain("do not edit truth docs or truth routing");
expect(agentsBlock).not.toContain("write functional code only");
expect(agentsBlock).not.toContain("do not edit truth docs or truth routing");
});
it("renders default standards only when comparable standards are missing", () => {