mirror of
https://github.com/merlinhu1/truthmark.git
synced 2026-08-25 07:53:25 +02:00
* feat: add interactive platform selection to init * chore: remove completed OpenSpec artifacts * chore: remove implemented design note * fix: protect init lifecycle from unsafe config paths * fix: support clearing init platforms from CLI * docs: retire config command from repository rules * docs: align init lifecycle and clear-platform contracts * docs: keep repository guidance current-state focused * docs: limit cleanup to repository-native documents * docs: preserve historical version notes --------- Co-authored-by: MerlinH <merlinh221@gmail.com>
355 lines
11 KiB
TypeScript
355 lines
11 KiB
TypeScript
import fs from "node:fs/promises";
|
|
|
|
import { describe, it } from "node:test";
|
|
import { expect } from "expect";
|
|
|
|
import { writeTruthmarkConfig } from "../helpers/truthmark-config.js";
|
|
import { runInit } from "../../src/init/init.js";
|
|
import type { LifecyclePlan } from "../../src/init/lifecycle.js";
|
|
import {
|
|
TRUTHMARK_BLOCK_END,
|
|
TRUTHMARK_BLOCK_START,
|
|
} from "../../src/templates/agents-block.js";
|
|
|
|
import { createTempRepo } from "../helpers/temp-repo.js";
|
|
|
|
describe("runInit instruction integration", () => {
|
|
it("installs a single managed AGENTS block with agent-native workflow guidance", async () => {
|
|
const repo = await createTempRepo();
|
|
|
|
try {
|
|
await writeTruthmarkConfig(repo.rootDir);
|
|
await repo.writeFile(
|
|
".truthmark/config.yml",
|
|
(await repo.readFile(".truthmark/config.yml")).replace(
|
|
"version: 2\n",
|
|
"version: 2\nplatforms:\n - codex\n",
|
|
),
|
|
);
|
|
await runInit(repo.rootDir);
|
|
|
|
const agents = await repo.readFile("AGENTS.md");
|
|
|
|
expect(agents.match(/<!-- truthmark:start -->/g)).toHaveLength(1);
|
|
expect(agents.match(/<!-- truthmark:end -->/g)).toHaveLength(1);
|
|
expect(agents.split("\n").length).toBeLessThanOrEqual(20);
|
|
expect(agents.slice(0, 220)).toContain("Truthmark Workflow");
|
|
expect(agents).not.toContain("/skill truthmark-sync");
|
|
expect(agents).not.toContain("OpenCode /skill truthmark-sync");
|
|
expect(agents).toContain("Truthmark-managed block");
|
|
expect(agents).not.toContain("Generated by Truthmark");
|
|
expect(agents).toContain("After functional code changes");
|
|
expect(agents).toContain("code changed -> tests -> Sync -> report");
|
|
expect(agents).toContain("Delegation is host-owned");
|
|
expect(agents).toContain(
|
|
"Explicit workflows: Truth Structure, Truth Document, 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",
|
|
);
|
|
expect(agents).not.toContain("Truth Structure: completed");
|
|
expect(agents).not.toContain("Truth Sync: completed");
|
|
expect(agents).not.toContain("Truth Sync: skipped");
|
|
expect(agents).not.toContain("Truth Realize: completed");
|
|
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();
|
|
} finally {
|
|
await repo.cleanup();
|
|
}
|
|
});
|
|
|
|
it("rerenders the managed block without duplicating it and leaves subagent choice to the host", async () => {
|
|
const repo = await createTempRepo();
|
|
|
|
try {
|
|
await writeTruthmarkConfig(repo.rootDir);
|
|
await repo.writeFile(
|
|
".truthmark/config.yml",
|
|
`version: 2
|
|
platforms:
|
|
- codex
|
|
truthmark:
|
|
workspace: docs/truthmark
|
|
generated:
|
|
portal:
|
|
enabled: false
|
|
frontmatter:
|
|
required: []
|
|
recommended:
|
|
- status
|
|
ignore: []
|
|
`,
|
|
);
|
|
await runInit(repo.rootDir);
|
|
await runInit(repo.rootDir);
|
|
|
|
const agents = await repo.readFile("AGENTS.md");
|
|
|
|
expect(agents.match(/<!-- truthmark:start -->/g)).toHaveLength(1);
|
|
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 changes need a fresh Sync review",
|
|
);
|
|
expect(agents).not.toContain("Explicit invocation:");
|
|
} finally {
|
|
await repo.cleanup();
|
|
}
|
|
});
|
|
|
|
it("ignores legacy instruction_targets and only writes platform-derived instruction surfaces", async () => {
|
|
const repo = await createTempRepo();
|
|
|
|
try {
|
|
await writeTruthmarkConfig(repo.rootDir);
|
|
await repo.writeFile(
|
|
".truthmark/config.yml",
|
|
`version: 2
|
|
platforms:
|
|
- codex
|
|
- claude-code
|
|
truthmark:
|
|
workspace: docs/truthmark
|
|
generated:
|
|
portal:
|
|
enabled: false
|
|
instruction_targets:
|
|
- src/session.ts
|
|
frontmatter:
|
|
required: []
|
|
recommended: []
|
|
ignore: []
|
|
`,
|
|
);
|
|
await repo.writeFile("src/session.ts", "export const session = true;\n");
|
|
|
|
const result = await runInit(repo.rootDir);
|
|
|
|
expect(
|
|
await repo.readFile("src/session.ts"),
|
|
).toBe("export const session = true;\n");
|
|
expect(await repo.readFile("AGENTS.md")).toContain("Truthmark Workflow");
|
|
expect(await repo.readFile("CLAUDE.md")).toContain("Truthmark Workflow");
|
|
expect(result.diagnostics).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
message: expect.stringContaining("instruction_targets"),
|
|
}),
|
|
]),
|
|
);
|
|
} finally {
|
|
await repo.cleanup();
|
|
}
|
|
});
|
|
|
|
it("rerenders managed AGENTS with CRLF user prefixes and suffixes preserved", async () => {
|
|
const repo = await createTempRepo();
|
|
|
|
try {
|
|
await writeTruthmarkConfig(repo.rootDir);
|
|
await repo.writeFile(
|
|
".truthmark/config.yml",
|
|
`version: 2
|
|
platforms:
|
|
- codex
|
|
truthmark:
|
|
workspace: docs/truthmark
|
|
generated:
|
|
portal:
|
|
enabled: false
|
|
frontmatter:
|
|
required: []
|
|
recommended:
|
|
- status
|
|
ignore: []
|
|
`,
|
|
);
|
|
await runInit(repo.rootDir);
|
|
|
|
const prefix = " before\r\n";
|
|
const suffix = "\r\n after \r\n\r\n";
|
|
const generated = await repo.readFile("AGENTS.md");
|
|
await repo.writeFile(
|
|
"AGENTS.md",
|
|
`${prefix}${generated.trimEnd().replace(/\n/g, "\r\n")}${suffix}`,
|
|
);
|
|
const result = await runInit(repo.rootDir);
|
|
|
|
expect(result.diagnostics).toEqual(
|
|
expect.not.arrayContaining([
|
|
expect.objectContaining({ severity: "error" }),
|
|
]),
|
|
);
|
|
const refreshed = await repo.readFile("AGENTS.md");
|
|
expect(refreshed.startsWith(prefix)).toBe(true);
|
|
expect(refreshed.endsWith(suffix)).toBe(true);
|
|
expect(refreshed).toContain("Truthmark Workflow");
|
|
expect(refreshed).toContain("Truthmark-managed block");
|
|
} finally {
|
|
await repo.cleanup();
|
|
}
|
|
});
|
|
|
|
for (const [caseName, malformedBlock] of [
|
|
[
|
|
"duplicate managed blocks",
|
|
`${TRUTHMARK_BLOCK_START}\nManaged 1\n${TRUTHMARK_BLOCK_START}\nManaged 2\n${TRUTHMARK_BLOCK_END}`,
|
|
],
|
|
[
|
|
"reversed managed markers",
|
|
`${TRUTHMARK_BLOCK_END}\nManaged 1\n${TRUTHMARK_BLOCK_START}\n`,
|
|
],
|
|
[
|
|
"unmatched managed start marker",
|
|
`${TRUTHMARK_BLOCK_START}\nManaged 1\n`,
|
|
],
|
|
[
|
|
"unmatched managed end marker",
|
|
`Managed 1\n${TRUTHMARK_BLOCK_END}\n`,
|
|
],
|
|
] as const) {
|
|
it(`blocks init when managed markers are ${caseName}`, async () => {
|
|
const repo = await createTempRepo();
|
|
|
|
try {
|
|
await writeTruthmarkConfig(repo.rootDir);
|
|
await repo.writeFile(
|
|
".truthmark/config.yml",
|
|
`version: 2
|
|
platforms:
|
|
- codex
|
|
truthmark:
|
|
workspace: docs/truthmark
|
|
generated:
|
|
portal:
|
|
enabled: false
|
|
frontmatter:
|
|
required: []
|
|
recommended:
|
|
- status
|
|
ignore: []
|
|
`,
|
|
);
|
|
await runInit(repo.rootDir);
|
|
await repo.writeFile("AGENTS.md", `${malformedBlock}\n`);
|
|
const before = await repo.readFile("AGENTS.md");
|
|
|
|
const result = await runInit(repo.rootDir);
|
|
|
|
expect(result.summary).toContain("preflight failed");
|
|
expect(await repo.readFile("AGENTS.md")).toBe(before);
|
|
expect(result.diagnostics).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
category: "generated-surface",
|
|
severity: "error",
|
|
message: expect.stringContaining("preflight"),
|
|
}),
|
|
]),
|
|
);
|
|
const lifecyclePlan = result.data?.lifecyclePlan as LifecyclePlan | undefined;
|
|
expect(lifecyclePlan?.entries).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
action: "manual-review",
|
|
path: "AGENTS.md",
|
|
}),
|
|
]),
|
|
);
|
|
} finally {
|
|
await repo.cleanup();
|
|
}
|
|
});
|
|
}
|
|
|
|
it("removes auto-removable retired generated surfaces but preserves stale Gemini surfaces", async () => {
|
|
const repo = await createTempRepo();
|
|
|
|
try {
|
|
await writeTruthmarkConfig(repo.rootDir);
|
|
await repo.writeFile(
|
|
".truthmark/config.yml",
|
|
(await repo.readFile(".truthmark/config.yml")).replace(
|
|
"version: 2\n",
|
|
"version: 2\nplatforms:\n - codex\n",
|
|
),
|
|
);
|
|
await runInit(repo.rootDir);
|
|
await repo.writeFile(
|
|
".agents/skills/truthmark-preview/SKILL.md",
|
|
"# Legacy Preview Skill\n",
|
|
);
|
|
await repo.writeFile(
|
|
".github/prompts/truthmark-preview.prompt.md",
|
|
"# Legacy Preview Prompt\n",
|
|
);
|
|
await repo.writeFile(
|
|
".gemini/commands/truthmark/preview.toml",
|
|
'description = "Legacy Preview Command"\n',
|
|
);
|
|
await repo.writeFile("GEMINI.md", "# Legacy Gemini instructions\n");
|
|
await repo.writeFile(
|
|
".gemini/skills/truthmark-sync/SKILL.md",
|
|
"# Legacy Gemini Sync Skill\n",
|
|
);
|
|
await repo.writeFile(
|
|
".gemini/agents/truth-doc-writer.md",
|
|
"# Legacy Gemini doc writer\n",
|
|
);
|
|
await repo.writeFile(
|
|
".gemini/commands/truthmark/sync.toml",
|
|
'description = "Legacy Sync Command"\n',
|
|
);
|
|
await repo.writeFile(
|
|
".agents/skills/truthmark-sync/helper-manifest.yml",
|
|
"id: truthmark-sync\n",
|
|
);
|
|
await repo.writeFile(
|
|
".opencode/skills/truthmark-sync/support/helper-policy.md",
|
|
"Legacy helper policy\n",
|
|
);
|
|
|
|
await runInit(repo.rootDir);
|
|
|
|
await expect(
|
|
fs.stat(`${repo.rootDir}/.agents/skills/truthmark-preview/SKILL.md`),
|
|
).resolves.toBeDefined();
|
|
await expect(
|
|
fs.stat(`${repo.rootDir}/.github/prompts/truthmark-preview.prompt.md`),
|
|
).resolves.toBeDefined();
|
|
await expect(
|
|
fs.stat(`${repo.rootDir}/.gemini/commands/truthmark/preview.toml`),
|
|
).resolves.toBeDefined();
|
|
await expect(fs.stat(`${repo.rootDir}/GEMINI.md`)).resolves.toBeDefined();
|
|
await expect(
|
|
fs.stat(`${repo.rootDir}/.gemini/skills/truthmark-sync/SKILL.md`),
|
|
).resolves.toBeDefined();
|
|
await expect(
|
|
fs.stat(`${repo.rootDir}/.gemini/agents/truth-doc-writer.md`),
|
|
).resolves.toBeDefined();
|
|
await expect(
|
|
fs.stat(`${repo.rootDir}/.gemini/commands/truthmark/sync.toml`),
|
|
).resolves.toBeDefined();
|
|
await expect(
|
|
fs.stat(
|
|
`${repo.rootDir}/.agents/skills/truthmark-sync/helper-manifest.yml`,
|
|
),
|
|
).resolves.toBeDefined();
|
|
await expect(
|
|
fs.stat(
|
|
`${repo.rootDir}/.opencode/skills/truthmark-sync/support/helper-policy.md`,
|
|
),
|
|
).resolves.toBeDefined();
|
|
} finally {
|
|
await repo.cleanup();
|
|
}
|
|
});
|
|
});
|