Files
truthmark/tests/init/init-instructions.test.ts
e3edcc8afa feat: v2.3.0 add interactive platform selection to init (#38)
* 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>
2026-07-31 18:54:10 +10:00

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();
}
});
});