mirror of
https://github.com/merlinhu1/truthmark.git
synced 2026-08-25 07:53:25 +02:00
feat: add canonical agent workflow package (#19)
* feat: add Truth Sync intent checklist * feat: add canonical agent workflow package Add the .truthmark/agent canonical workflow package, convert duplicated host workflow bodies into adapter/expanded-adapter surfaces, and validate canonical/adapter freshness in Truthmark check. Refresh generated surfaces and package metadata for Truthmark 2.3.0, with OpenSpec tasks completed and release notes added. * fix: preserve native workflow skill packages * docs: record native skill package decision * fix: drop unused agent workflow package * chore: remove implemented OpenSpec docs * chore: release workflow fixes as 2.2.2 * fix: remove agent-visible version markers * fix: drop generated surface version markers * feat: capture decision context in Truth Sync --------- Co-authored-by: MerlinH <merlinh221@gmail.com>
This commit is contained in:
@@ -24,7 +24,7 @@ describe("renderTruthSyncInstructions", () => {
|
||||
);
|
||||
expect(instructions).toContain("documentation-only change");
|
||||
expect(instructions).toContain("Explicit invocation runs immediately");
|
||||
expect(instructions).toContain("Later functional-code changes reopen the finish-time requirement");
|
||||
expect(instructions).toContain("Later functional-code changes need a fresh finish-time review");
|
||||
expect(instructions).toContain("must not rewrite functional code");
|
||||
expect(instructions).toContain("host supports subagent dispatch");
|
||||
expect(instructions).toContain("WorkflowState and ImpactSet are optional compact derived context");
|
||||
@@ -32,7 +32,7 @@ describe("renderTruthSyncInstructions", () => {
|
||||
"If routing is missing, stale, broad, overloaded, catch-all, or cannot map changed code to a bounded truth owner",
|
||||
);
|
||||
expect(instructions).toContain("run Truth Structure before syncing");
|
||||
expect(instructions).toContain("otherwise block and recommend Truth Structure");
|
||||
expect(instructions).toContain("otherwise stop and recommend Truth Structure");
|
||||
expect(instructions).not.toContain(".truthmark/local.yml");
|
||||
expect(instructions).not.toContain("truth_sync.sync_agent");
|
||||
expect(instructions).not.toContain("Truth Sync: completed");
|
||||
|
||||
@@ -13,7 +13,7 @@ describe("renderTruthRealizePrompt", () => {
|
||||
expect(prompt).toContain(
|
||||
"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("Truth-doc ownership review");
|
||||
expect(prompt).toContain(
|
||||
"if a source truth doc is broad, mixed-owner, index-like, unrouteable, stale, or conflicts with implementation evidence",
|
||||
);
|
||||
@@ -35,7 +35,7 @@ describe("renderTruthRealizePrompt", () => {
|
||||
expect(prompt).not.toContain("product-lane writes belong");
|
||||
expect(prompt).not.toContain("engineering-lane writes belong");
|
||||
expect(prompt).not.toContain("Truth-doc restructure gate");
|
||||
expect(prompt).not.toContain("Truth-doc shape repair gate");
|
||||
expect(prompt).not.toContain("Truth-doc shape repair review");
|
||||
expect(prompt).not.toContain("restructure truth docs");
|
||||
expect(prompt).toContain("Report changed code files and verification steps");
|
||||
expect(prompt).toContain("installed instruction or skill");
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
renderTruthmarkCopilotRouteAuditorAgent,
|
||||
renderTruthmarkRouteAuditorAgent,
|
||||
} from "../../src/templates/workflow-surfaces.js";
|
||||
import { TRUTHMARK_VERSION } from "../../src/version.js";
|
||||
|
||||
describe("renderTruthCheckSkillBody", () => {
|
||||
it("renders the agent-led truth audit workflow", () => {
|
||||
@@ -35,7 +34,6 @@ describe("renderTruthCheckSkillBody", () => {
|
||||
|
||||
expect(TRUTH_CHECK_EXPLICIT_INVOCATIONS).toContain("/truthmark:check");
|
||||
expect(skill).toContain("name: truthmark-check");
|
||||
expect(skill).toContain(`truthmark-version: ${TRUTHMARK_VERSION}`);
|
||||
expect(skill).toContain(
|
||||
"description: Use when the user asks to audit repository truth health, routing, ownership, or canonical docs.",
|
||||
);
|
||||
@@ -124,7 +122,7 @@ describe("Truth Check generated surfaces", () => {
|
||||
"allow_implicit_invocation: false",
|
||||
);
|
||||
expect(renderTruthmarkCheckSkillMetadata()).toContain(
|
||||
`version: "${TRUTHMARK_VERSION}"`,
|
||||
'refresh_command: "truthmark init"',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
renderTruthmarkDocumentSkillMetadata,
|
||||
renderTruthmarkGeminiDocumentCommand,
|
||||
} from "../../src/templates/workflow-surfaces.js";
|
||||
import { TRUTHMARK_VERSION } from "../../src/version.js";
|
||||
|
||||
describe("renderTruthDocumentSkillBody", () => {
|
||||
it("renders parseable skill frontmatter", () => {
|
||||
@@ -40,7 +39,6 @@ describe("renderTruthDocumentSkillBody", () => {
|
||||
|
||||
expect(TRUTH_DOCUMENT_EXPLICIT_INVOCATIONS).toContain("/truthmark:document");
|
||||
expect(skill).toContain("name: truthmark-document");
|
||||
expect(skill).toContain(`truthmark-version: ${TRUTHMARK_VERSION}`);
|
||||
expect(skill).toContain("manual and implementation-first");
|
||||
expect(skill).toContain("existing implemented behavior");
|
||||
expect(skill).toContain("no functional-code changes");
|
||||
@@ -49,15 +47,15 @@ describe("renderTruthDocumentSkillBody", () => {
|
||||
expect(skill).toContain("When creating or updating a truth doc");
|
||||
expect(skill).toContain("HTML comments under each template section");
|
||||
expect(skill).toContain("normative authoring guidance");
|
||||
expect(skill).toContain("Truth-doc ownership gate");
|
||||
expect(skill).toContain("Truth-doc ownership review");
|
||||
expect(skill).toContain(
|
||||
"if the target doc is broad, mixed-owner, index-like, or the documented behavior spans independent owners",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"report Ownership reviewed, Structure required, Truth docs split, Truth docs restructured, or Blocked reason",
|
||||
"report Ownership reviewed, Structure required, Truth docs split, Truth docs restructured, or Manual handoff reason",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"Decision/Rationale preservation gate",
|
||||
"Decision/Rationale preservation review",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"before any truth-doc split, restructure, or shape repair, inventory existing Product Decisions, Engineering Decisions, and Rationale sections",
|
||||
@@ -66,9 +64,9 @@ describe("renderTruthDocumentSkillBody", () => {
|
||||
"preserve each current decision and rationale in the correct product or engineering lane owner",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"if ownership of a decision or rationale is unclear, block with manual-review files",
|
||||
"if ownership of a decision or rationale is unclear, stop with manual-review files",
|
||||
);
|
||||
expect(skill).toContain("Truth-doc shape repair gate");
|
||||
expect(skill).toContain("Truth-doc shape repair review");
|
||||
expect(skill).toContain(
|
||||
"Truth Document may restructure only truth docs for the implemented behavior being documented.",
|
||||
);
|
||||
@@ -87,14 +85,14 @@ describe("renderTruthDocumentSkillBody", () => {
|
||||
"run Truth Structure first when routing repair is safe and in scope",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"block and recommend Truth Structure when routing repair is unsafe, ambiguous, or outside the task boundary",
|
||||
"stop and recommend Truth Structure when routing repair is unsafe, ambiguous, or outside the task boundary",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"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 WorkflowState/action context");
|
||||
expect(skill).toContain("repository-intelligence artifacts were not generated");
|
||||
expect(skill).toContain("Evidence Gate");
|
||||
expect(skill).toContain("Evidence checklist");
|
||||
expect(skill).toContain(
|
||||
"route-first: map the documented behavior to bounded route owners and primary canonical docs",
|
||||
);
|
||||
@@ -102,7 +100,7 @@ describe("renderTruthDocumentSkillBody", () => {
|
||||
"support claims with primary checkout evidence",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"remove, narrow, or block unsupported claims",
|
||||
"remove, narrow, or record unsupported claims for manual handoff",
|
||||
);
|
||||
expect(skill).toContain("Truth Document: completed");
|
||||
expect(skill).toContain("Implementation reviewed");
|
||||
@@ -188,7 +186,7 @@ describe("Truth Document generated surfaces", () => {
|
||||
"allow_implicit_invocation: false",
|
||||
);
|
||||
expect(renderTruthmarkDocumentSkillMetadata()).toContain(
|
||||
`version: "${TRUTHMARK_VERSION}"`,
|
||||
'refresh_command: "truthmark init"',
|
||||
);
|
||||
expect(renderTruthmarkGeminiDocumentCommand()).toContain(
|
||||
"This command is the Gemini CLI entrypoint for Truthmark Document.",
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
renderTruthmarkPreviewSkill,
|
||||
renderTruthmarkPreviewSkillMetadata,
|
||||
} from "../../src/templates/workflow-surfaces.js";
|
||||
import { TRUTHMARK_VERSION } from "../../src/version.js";
|
||||
|
||||
describe("renderTruthPreviewSkillBody", () => {
|
||||
it("renders a thin explicit read-only preview workflow", () => {
|
||||
@@ -17,7 +16,6 @@ describe("renderTruthPreviewSkillBody", () => {
|
||||
|
||||
expect(TRUTH_PREVIEW_EXPLICIT_INVOCATIONS).toContain("/truthmark:preview");
|
||||
expect(skill).toContain("name: truthmark-preview");
|
||||
expect(skill).toContain(`truthmark-version: ${TRUTHMARK_VERSION}`);
|
||||
expect(skill).toContain("Truth Preview is read-only");
|
||||
expect(skill).toContain("intended, not authorized");
|
||||
expect(skill).toContain("must not edit files");
|
||||
@@ -32,7 +30,7 @@ describe("renderTruthPreviewSkillBody", () => {
|
||||
expect(skill).toContain("Likely route owner");
|
||||
expect(skill).toContain("Expected write classes");
|
||||
expect(skill).toContain("Suggested subagent use");
|
||||
expect(skill).toContain("Blocking ambiguity");
|
||||
expect(skill).toContain("Manual handoff questions");
|
||||
expect(skill).toContain("Handoff:");
|
||||
expect(skill).toContain("truth_route_auditor");
|
||||
expect(skill).not.toContain("truth_doc_writer");
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
renderTruthmarkStructureSkill,
|
||||
renderTruthmarkStructureSkillMetadata,
|
||||
} from "../../src/templates/workflow-surfaces.js";
|
||||
import { TRUTHMARK_VERSION } from "../../src/version.js";
|
||||
|
||||
describe("renderTruthStructureSkillBody", () => {
|
||||
it("renders parseable skill frontmatter", () => {
|
||||
@@ -37,7 +36,7 @@ describe("renderTruthStructureSkillBody", () => {
|
||||
const lines = skill.split("\n");
|
||||
|
||||
expect(lines[0]).toBe("---");
|
||||
expect(lines[6]).toBe("---");
|
||||
expect(lines[5]).toBe("---");
|
||||
expect(skill).toContain(
|
||||
"Starter truth docs must use closed YAML frontmatter bounded by opening and closing --- lines; include status, truth_kind, and last_reviewed inside that frontmatter. Put source references in the final ## Source References section, not in frontmatter.",
|
||||
);
|
||||
@@ -50,11 +49,11 @@ describe("renderTruthStructureSkillBody", () => {
|
||||
expect(skill).toContain("Align existing docs to that template");
|
||||
expect(skill).toContain("HTML comments under each template section");
|
||||
expect(skill).toContain("normative authoring guidance");
|
||||
expect(skill).toContain("Truth-doc ownership gate");
|
||||
expect(skill).toContain("Truth-doc ownership review");
|
||||
expect(skill).toContain(
|
||||
"if a truth doc mixes independent owners, route ownership is broad, or a split is required for bounded ownership",
|
||||
);
|
||||
expect(skill).toContain("Decision/Rationale preservation gate");
|
||||
expect(skill).toContain("Decision/Rationale preservation review");
|
||||
expect(skill).toContain(
|
||||
"before any truth-doc split, restructure, or shape repair, inventory existing Product Decisions, Engineering Decisions, and Rationale sections",
|
||||
);
|
||||
@@ -62,10 +61,10 @@ describe("renderTruthStructureSkillBody", () => {
|
||||
"preserve each current decision and rationale in the correct product or engineering lane owner",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"if ownership of a decision or rationale is unclear, block with manual-review files",
|
||||
"if ownership of a decision or rationale is unclear, stop with manual-review files",
|
||||
);
|
||||
expect(skill).toContain("Truth docs split");
|
||||
expect(skill).toContain("Truth-doc shape repair gate");
|
||||
expect(skill).toContain("Truth-doc shape repair review");
|
||||
expect(skill).toContain(
|
||||
"Truth Structure may restructure broader routed docs when topology, ownership, or doc-shape repair is already in scope.",
|
||||
);
|
||||
@@ -79,7 +78,6 @@ describe("renderTruthStructureSkillBody", () => {
|
||||
"/truthmark:structure",
|
||||
);
|
||||
expect(skill).toContain("name: truthmark-structure");
|
||||
expect(skill).toContain(`truthmark-version: ${TRUTHMARK_VERSION}`);
|
||||
expect(skill).toContain("inspect repository layout");
|
||||
expect(skill).toContain(
|
||||
"Repository instruction files and explicitly configured policy docs remain instruction authority when present; do not assume a repository uses any particular policy path.",
|
||||
@@ -104,12 +102,12 @@ describe("renderTruthStructureSkillBody", () => {
|
||||
expect(skill).toContain(
|
||||
"If an existing canonical doc has wrong-lane sections, split or move them into the correct product or engineering lane.",
|
||||
);
|
||||
expect(skill).toContain("Evidence Gate");
|
||||
expect(skill).toContain("apply the Evidence Gate before finishing");
|
||||
expect(skill).toContain("Evidence checklist");
|
||||
expect(skill).toContain("apply the evidence checklist before finishing");
|
||||
expect(skill).toContain(
|
||||
"support ownership/behavior claims with topology or primary checkout evidence",
|
||||
);
|
||||
expect(skill).toContain("remove, narrow, or block unsupported claims");
|
||||
expect(skill).toContain("remove, narrow, or record unsupported or stale claims for manual handoff");
|
||||
expect(skill).toContain(
|
||||
"date active decisions inline when added or changed",
|
||||
);
|
||||
@@ -140,7 +138,7 @@ describe("renderTruthStructureSkillBody", () => {
|
||||
expect(skill).toContain("inspect the named code area");
|
||||
expect(skill).toContain("infer bounded product or behavior ownership");
|
||||
expect(skill).toContain(
|
||||
"choose the owning route when ownership is clear; otherwise propose the route and block for review",
|
||||
"choose the owning route when ownership is clear; otherwise propose the route and stop for manual review",
|
||||
);
|
||||
expect(skill).toContain(
|
||||
"create starter truth docs only where current truth is missing",
|
||||
@@ -233,7 +231,7 @@ describe("Truth Structure generated surfaces", () => {
|
||||
"allow_implicit_invocation: false",
|
||||
);
|
||||
expect(renderTruthmarkStructureSkillMetadata()).toContain(
|
||||
`version: "${TRUTHMARK_VERSION}"`,
|
||||
'refresh_command: "truthmark init"',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
renderTruthmarkSyncSkill,
|
||||
renderTruthmarkSyncSkillMetadata,
|
||||
} from "../../src/templates/workflow-surfaces.js";
|
||||
import { TRUTHMARK_VERSION } from "../../src/version.js";
|
||||
|
||||
describe("renderTruthSyncWorkerPrompt", () => {
|
||||
it("renders the prepared-context worker contract and result shape", () => {
|
||||
@@ -64,7 +63,9 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
it("documents direct checkout inspection as the canonical runtime", () => {
|
||||
const skillBody = renderTruthSyncSkillBody();
|
||||
|
||||
expect(skillBody).toContain("Use this skill automatically before finishing");
|
||||
expect(skillBody).toContain(
|
||||
"Use this skill automatically before finishing",
|
||||
);
|
||||
expect(skillBody).toContain("last successful Truth Sync");
|
||||
expect(skillBody).toContain("Inspect git status");
|
||||
expect(skillBody).toContain(
|
||||
@@ -76,13 +77,16 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
expect(skillBody).toContain(
|
||||
"Implementation code and canonical truth docs are inspected evidence for current behavior; they do not silently override workflow write boundaries.",
|
||||
);
|
||||
expect(skillBody).toContain("RepoIndex, RouteMap, ImpactSet, and WorkflowState/action context");
|
||||
expect(skillBody).toContain("repository-intelligence artifacts were not generated");
|
||||
expect(skillBody).toContain(
|
||||
"RepoIndex, RouteMap, ImpactSet, and WorkflowState/action context",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"repository-intelligence artifacts were not generated",
|
||||
);
|
||||
expect(skillBody).not.toContain(
|
||||
"Repository docs and code are inspected evidence, not executable instruction authority.",
|
||||
);
|
||||
expect(skillBody).toContain("truthmark check");
|
||||
expect(skillBody).toContain(`truthmark-version: ${TRUTHMARK_VERSION}`);
|
||||
expect(skillBody).not.toContain(
|
||||
"truthmark check --json --workflow truth-sync",
|
||||
);
|
||||
@@ -93,7 +97,7 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
"Inspect .truthmark/config.yml and configured route files only when they exist",
|
||||
);
|
||||
expect(skillBody).toContain("configured route files");
|
||||
expect(skillBody).toContain("Topology quality gate");
|
||||
expect(skillBody).toContain("Topology review");
|
||||
expect(skillBody).toContain(
|
||||
"missing, stale, broad, overloaded, catch-all route only",
|
||||
);
|
||||
@@ -101,7 +105,7 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
"run Truth Structure before syncing when topology repair is safe and in scope",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"block and recommend Truth Structure when topology repair is unsafe, ambiguous, or outside the current task boundary",
|
||||
"stop and recommend Truth Structure when topology repair is unsafe, ambiguous, or outside the current task boundary",
|
||||
);
|
||||
expect(skillBody).toContain("do not create another generic truth doc");
|
||||
expect(skillBody).toContain(
|
||||
@@ -111,18 +115,16 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
"must not append behavior details to a README.md index",
|
||||
);
|
||||
expect(skillBody).toContain("create or update a bounded leaf truth doc");
|
||||
expect(skillBody).toContain("Evidence Gate");
|
||||
expect(skillBody).toContain("Evidence checklist");
|
||||
expect(skillBody).toContain(
|
||||
"route-first: map changed functional files to bounded route owners and primary canonical docs",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"support claims with primary checkout evidence",
|
||||
);
|
||||
expect(skillBody).toContain("tests/examples/canonical docs corroborate");
|
||||
expect(skillBody).toContain(
|
||||
"tests/examples/canonical docs corroborate",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"remove, narrow, or block unsupported claims",
|
||||
"remove, narrow, or record unsupported claims for manual handoff",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"Maintain architecture docs only for structure-level changes",
|
||||
@@ -136,16 +138,14 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
expect(skillBody).toContain("Align existing docs to that template");
|
||||
expect(skillBody).toContain("HTML comments under each template section");
|
||||
expect(skillBody).toContain("normative authoring guidance");
|
||||
expect(skillBody).toContain("Truth-doc ownership gate");
|
||||
expect(skillBody).toContain("Truth-doc ownership review");
|
||||
expect(skillBody).toContain(
|
||||
"if an impacted doc is broad, mixed-owner, index-like, or the update spans independent behavior owners",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"report Ownership reviewed, Structure required, Truth docs split, Truth docs restructured, or Blocked reason",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"Decision/Rationale preservation gate",
|
||||
"report Ownership reviewed, Structure required, Truth docs split, Truth docs restructured, or Manual handoff reason",
|
||||
);
|
||||
expect(skillBody).toContain("Decision/Rationale preservation review");
|
||||
expect(skillBody).toContain(
|
||||
"before any truth-doc split, restructure, or shape repair, inventory existing Product Decisions, Engineering Decisions, and Rationale sections",
|
||||
);
|
||||
@@ -153,9 +153,9 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
"preserve each current decision and rationale in the correct product or engineering lane owner",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"if ownership of a decision or rationale is unclear, block with manual-review files",
|
||||
"if ownership of a decision or rationale is unclear, stop with manual-review files",
|
||||
);
|
||||
expect(skillBody).toContain("Truth-doc shape repair gate");
|
||||
expect(skillBody).toContain("Truth-doc shape repair review");
|
||||
expect(skillBody).toContain(
|
||||
"Truth Sync may restructure leased canonical truth docs when the current sync evidence shows repository truth is stale",
|
||||
);
|
||||
@@ -163,13 +163,21 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
expect(skillBody).toContain(
|
||||
"repair shape when a narrow edit would make truth worse",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"report docs restructured and why",
|
||||
);
|
||||
expect(skillBody).toContain("report docs restructured and why");
|
||||
expect(skillBody).not.toContain("# {{title}}");
|
||||
expect(skillBody).toContain(
|
||||
"update Product Decisions only in product truth and Engineering Decisions only in engineering truth when evidence supports the lane-specific decision change",
|
||||
);
|
||||
expect(skillBody).toContain("Fill Sync Intent before editing truth docs");
|
||||
expect(skillBody).toContain("Sync Intent");
|
||||
expect(skillBody).toContain("Affected route/truth owner");
|
||||
expect(skillBody).toContain("User-provided decisions/rationale");
|
||||
expect(skillBody).toContain("Decision/rationale captured");
|
||||
expect(skillBody).toContain("Preserve concise user-provided decision rationale");
|
||||
expect(skillBody).toContain("No-update-needed rationale");
|
||||
expect(skillBody).toContain(
|
||||
"Only edit allowed truth docs/routes after Sync Intent is clear",
|
||||
);
|
||||
expect(skillBody).toContain("Evidence checked");
|
||||
expect(skillBody).toContain("Claim:");
|
||||
expect(skillBody).toContain("Result: supported");
|
||||
@@ -179,7 +187,7 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
"may write truth docs and docs/truthmark/routes/areas.md only for Truth Sync alignment",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"verify the final report records ownership review, structure requirement, split, restructure, or blocked reason",
|
||||
"verify the final report records ownership review, structure requirement, split, restructure, or manual handoff reason",
|
||||
);
|
||||
expect(skillBody).toContain(
|
||||
"verify the updated docs correspond to reviewed checkout evidence, changed-code impact, or a recorded stale-truth correction",
|
||||
@@ -194,7 +202,9 @@ describe("renderTruthSyncSkillBody", () => {
|
||||
|
||||
const skillBody = renderTruthSyncSkillBody(config);
|
||||
|
||||
expect(skillBody).toContain("docs/truthmark/engineering/repository/overview.md");
|
||||
expect(skillBody).toContain(
|
||||
"docs/truthmark/engineering/repository/bootstrap-routing.md",
|
||||
);
|
||||
expect(skillBody).toContain("docs/routes/index.md:11");
|
||||
expect(skillBody).toContain(
|
||||
"verify only truth docs and leased truth routing files changed",
|
||||
@@ -236,7 +246,6 @@ describe("Truth Sync generated metadata", () => {
|
||||
expect(source).not.toContain("endsWith(reportTemplate)");
|
||||
});
|
||||
|
||||
|
||||
it("renders every workflow package from structured procedure and report support files", () => {
|
||||
const workflowIds: TruthmarkWorkflowId[] = [
|
||||
"truthmark-structure",
|
||||
@@ -254,8 +263,9 @@ describe("Truth Sync generated metadata", () => {
|
||||
workflowId,
|
||||
host: "codex",
|
||||
});
|
||||
const entrypoint = files.find((file) => file.path.endsWith("/SKILL.md"))
|
||||
?.content;
|
||||
const entrypoint = files.find((file) =>
|
||||
file.path.endsWith("/SKILL.md"),
|
||||
)?.content;
|
||||
const procedure = files.find((file) =>
|
||||
file.path.endsWith("/support/procedure.md"),
|
||||
)?.content;
|
||||
@@ -266,7 +276,9 @@ describe("Truth Sync generated metadata", () => {
|
||||
expect(entrypoint, workflowId).toContain("Progressive disclosure:");
|
||||
expect(entrypoint, workflowId).toContain("support/procedure.md");
|
||||
expect(entrypoint, workflowId).toContain("support/report-template.md");
|
||||
expect(procedure, workflowId).toContain("Generated by Truthmark");
|
||||
expect(procedure, workflowId).toContain(
|
||||
"Truthmark-managed generated file",
|
||||
);
|
||||
expect(procedure, workflowId).not.toContain(
|
||||
"Report completion in this shape:",
|
||||
);
|
||||
@@ -300,6 +312,9 @@ describe("Truth Sync generated metadata", () => {
|
||||
);
|
||||
expect(procedure).not.toContain("Truth Sync: completed");
|
||||
expect(reportTemplate).toContain("Report completion in this shape:");
|
||||
expect(reportTemplate).toContain("Sync Intent:");
|
||||
expect(reportTemplate).toContain("Affected route/truth owner");
|
||||
expect(reportTemplate).toContain("No-update-needed rationale");
|
||||
expect(reportTemplate).toContain("Truth Sync: completed");
|
||||
expect(reportTemplate).not.toContain("Parent post-sync verification");
|
||||
});
|
||||
@@ -330,7 +345,9 @@ describe("Truth Sync generated metadata", () => {
|
||||
expect(surface).toContain(
|
||||
"If skill entrypoints are unavailable, use the host's direct evidence-first manual fallback procedure.",
|
||||
);
|
||||
expect(surface).toContain("Do not invoke another Truthmark command from here.");
|
||||
expect(surface).toContain(
|
||||
"Do not invoke another Truthmark command from here.",
|
||||
);
|
||||
expect(surface).toContain("support/procedure.md");
|
||||
expect(surface).toContain("support/report-template.md");
|
||||
expect(surface).toContain("helper-manifest.yml");
|
||||
@@ -371,11 +388,17 @@ describe("Truth Sync generated metadata", () => {
|
||||
expect(renderTruthmarkSyncClaudeSkill()).toContain(
|
||||
"Parent agent owns Truth Sync acceptance, lease validation, and final report",
|
||||
);
|
||||
expect(renderTruthmarkSyncLocalSkill()).not.toContain("Codex subagent mode:");
|
||||
expect(renderTruthmarkSyncLocalSkill()).not.toContain(
|
||||
"Codex subagent mode:",
|
||||
);
|
||||
expect(renderTruthmarkSyncLocalSkill()).not.toContain(
|
||||
"Claude Code subagent mode:",
|
||||
);
|
||||
expect(renderTruthmarkGeminiSyncCommand()).not.toContain("Codex subagent mode:");
|
||||
expect(renderTruthmarkCopilotSyncPrompt()).not.toContain("Codex subagent mode:");
|
||||
expect(renderTruthmarkGeminiSyncCommand()).not.toContain(
|
||||
"Codex subagent mode:",
|
||||
);
|
||||
expect(renderTruthmarkCopilotSyncPrompt()).not.toContain(
|
||||
"Codex subagent mode:",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -137,6 +137,9 @@ Ownership reviewed:
|
||||
Truth docs updated:
|
||||
- docs/truthmark/truth/init-and-scaffold.md
|
||||
|
||||
Decision/rationale captured:
|
||||
- none provided in task conversation
|
||||
|
||||
Evidence checked:
|
||||
${evidenceChecked}
|
||||
|
||||
@@ -302,6 +305,9 @@ Ownership reviewed:
|
||||
Truth docs updated:
|
||||
- docs/truthmark/truth/init-and-scaffold.md
|
||||
|
||||
Decision/rationale captured:
|
||||
- none provided in task conversation
|
||||
|
||||
Evidence checked:
|
||||
- Claim: Init writes generated workflow files.
|
||||
Evidence: src/init/init.ts
|
||||
@@ -376,6 +382,9 @@ Ownership reviewed:
|
||||
Truth docs updated:
|
||||
- docs/truthmark/truth/init-and-scaffold.md
|
||||
|
||||
Decision/rationale captured:
|
||||
- none provided in task conversation
|
||||
|
||||
Evidence checked:
|
||||
- Claim: Init writes generated workflow files.
|
||||
Evidence: src/init/init.ts
|
||||
@@ -449,6 +458,9 @@ Ownership reviewed:
|
||||
Truth docs updated:
|
||||
- docs/truthmark/truth/init-and-scaffold.md
|
||||
|
||||
Decision/rationale captured:
|
||||
- none provided in task conversation
|
||||
|
||||
Evidence checked:
|
||||
- malformed entry only
|
||||
|
||||
@@ -480,6 +492,9 @@ Ownership reviewed:
|
||||
Truth docs updated:
|
||||
- docs/truthmark/truth/init-and-scaffold.md
|
||||
|
||||
Decision/rationale captured:
|
||||
- none provided in task conversation
|
||||
|
||||
Notes:
|
||||
- Missing evidence.
|
||||
`,
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseFrontmatter } from "../../src/markdown/frontmatter.js";
|
||||
|
||||
import {
|
||||
TRUTHMARK_WORKFLOW_IDS,
|
||||
TRUTHMARK_WORKFLOW_MANIFEST,
|
||||
getTruthmarkWorkflow,
|
||||
} from "../../src/agents/workflow-manifest.js";
|
||||
import { renderTruthCheckSkillBody } from "../../src/agents/truth-check.js";
|
||||
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 {
|
||||
renderTruthmarkPortalSkill,
|
||||
renderTruthmarkRealizeSkill,
|
||||
} from "../../src/templates/workflow-surfaces.js";
|
||||
|
||||
const renderWorkflowSkill = (id: (typeof TRUTHMARK_WORKFLOW_IDS)[number]) => {
|
||||
switch (id) {
|
||||
case "truthmark-sync":
|
||||
return renderTruthSyncSkillBody();
|
||||
case "truthmark-structure":
|
||||
return renderTruthStructureSkillBody();
|
||||
case "truthmark-document":
|
||||
return renderTruthDocumentSkillBody();
|
||||
case "truthmark-preview":
|
||||
return renderTruthPreviewSkillBody();
|
||||
case "truthmark-realize":
|
||||
return renderTruthmarkRealizeSkill();
|
||||
case "truthmark-check":
|
||||
return renderTruthCheckSkillBody();
|
||||
case "truthmark-portal":
|
||||
return renderTruthmarkPortalSkill();
|
||||
}
|
||||
};
|
||||
|
||||
describe("Truthmark workflow manifest", () => {
|
||||
it("is the source for generated skill frontmatter descriptions", () => {
|
||||
for (const id of TRUTHMARK_WORKFLOW_IDS) {
|
||||
const parsed = parseFrontmatter(renderWorkflowSkill(id));
|
||||
|
||||
expect(parsed.data.name).toBe(id);
|
||||
expect(parsed.data.description).toBe(
|
||||
getTruthmarkWorkflow(id).description,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("defines routing eval cases for every workflow", () => {
|
||||
for (const id of TRUTHMARK_WORKFLOW_IDS) {
|
||||
const workflow = TRUTHMARK_WORKFLOW_MANIFEST[id];
|
||||
|
||||
expect(workflow.positiveTriggers.length).toBeGreaterThan(0);
|
||||
expect(workflow.negativeTriggers.length).toBeGreaterThan(0);
|
||||
expect(workflow.forbiddenAdjacency.length).toBeGreaterThan(0);
|
||||
expect(workflow.requiredGates.length).toBeGreaterThan(0);
|
||||
expect(workflow.allowedWrites.length).toBeGreaterThan(0);
|
||||
expect(workflow.reportSections.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("declares optional helper metadata with manual fallbacks", () => {
|
||||
const sync = getTruthmarkWorkflow("truthmark-sync");
|
||||
const document = getTruthmarkWorkflow("truthmark-document");
|
||||
|
||||
expect(sync.helpers?.map((helper) => helper.id)).toEqual([
|
||||
"validate-sync-report",
|
||||
"validate-write-lease",
|
||||
]);
|
||||
expect(document.helpers?.map((helper) => helper.id)).toEqual([
|
||||
"validate-document-report",
|
||||
"validate-write-lease",
|
||||
]);
|
||||
|
||||
for (const workflow of [sync, document]) {
|
||||
expect(workflow.reportSections).toContain("Helper scripts");
|
||||
|
||||
for (const helper of workflow.helpers ?? []) {
|
||||
expect(helper.optional).toBe(true);
|
||||
expect(helper.runner).toMatch(/^truthmark>=/u);
|
||||
expect(helper.command.argv).toEqual(
|
||||
expect.arrayContaining(["truthmark", "validate", "--json"]),
|
||||
);
|
||||
expect(helper.command.argv.join(" ")).not.toContain("node scripts/");
|
||||
expect(helper.inputs.length).toBeGreaterThan(0);
|
||||
expect(helper.output).toBe("json");
|
||||
expect(helper.writes).toBe(false);
|
||||
expect(helper.fallback).toMatch(/manual/i);
|
||||
}
|
||||
}
|
||||
|
||||
expect(getTruthmarkWorkflow("truthmark-preview").helpers).toBeUndefined();
|
||||
});
|
||||
|
||||
it("defines read-only and write-capable subagent recommendations by workflow", () => {
|
||||
expect(TRUTHMARK_WORKFLOW_MANIFEST["truthmark-preview"].subagents).toEqual([
|
||||
"truth_route_auditor",
|
||||
]);
|
||||
expect(
|
||||
getTruthmarkWorkflow("truthmark-preview").writeSubagents,
|
||||
).toBeUndefined();
|
||||
expect(TRUTHMARK_WORKFLOW_MANIFEST["truthmark-check"].subagents).toEqual([
|
||||
"truth_route_auditor",
|
||||
"truth_claim_verifier",
|
||||
"truth_doc_reviewer",
|
||||
]);
|
||||
expect(
|
||||
getTruthmarkWorkflow("truthmark-check").writeSubagents,
|
||||
).toBeUndefined();
|
||||
expect(TRUTHMARK_WORKFLOW_MANIFEST["truthmark-document"].subagents).toEqual(
|
||||
["truth_route_auditor", "truth_claim_verifier"],
|
||||
);
|
||||
expect(
|
||||
TRUTHMARK_WORKFLOW_MANIFEST["truthmark-document"].writeSubagents,
|
||||
).toEqual(["truth_doc_writer"]);
|
||||
expect(TRUTHMARK_WORKFLOW_MANIFEST["truthmark-sync"].subagents).toEqual([
|
||||
"truth_route_auditor",
|
||||
"truth_claim_verifier",
|
||||
]);
|
||||
expect(
|
||||
TRUTHMARK_WORKFLOW_MANIFEST["truthmark-sync"].writeSubagents,
|
||||
).toEqual(["truth_doc_writer"]);
|
||||
expect(
|
||||
TRUTHMARK_WORKFLOW_MANIFEST["truthmark-structure"].subagents,
|
||||
).toEqual(["truth_route_auditor"]);
|
||||
expect(
|
||||
getTruthmarkWorkflow("truthmark-structure").writeSubagents,
|
||||
).toBeUndefined();
|
||||
expect(getTruthmarkWorkflow("truthmark-realize").subagents).toBeUndefined();
|
||||
expect(
|
||||
getTruthmarkWorkflow("truthmark-realize").writeSubagents,
|
||||
).toBeUndefined();
|
||||
});
|
||||
it("defines Truth Preview as an explicit read-only planning surface", () => {
|
||||
const workflow = getTruthmarkWorkflow(
|
||||
"truthmark-preview" as (typeof TRUTHMARK_WORKFLOW_IDS)[number],
|
||||
);
|
||||
|
||||
expect(workflow.displayName).toBe("Truthmark Preview");
|
||||
expect(workflow.allowImplicitInvocation).toBe(false);
|
||||
expect(workflow.allowedWrites).toEqual(["none by default"]);
|
||||
expect(workflow.reportSections).toEqual([
|
||||
"Requested outcome",
|
||||
"Likely workflow",
|
||||
"Why this workflow",
|
||||
"Likely route owner",
|
||||
"Expected write classes",
|
||||
"Expected target files",
|
||||
"Suggested subagent use",
|
||||
"Blocking ambiguity",
|
||||
"Handoff",
|
||||
]);
|
||||
});
|
||||
|
||||
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 the Truthmark Portal output",
|
||||
]),
|
||||
);
|
||||
expect(workflow.negativeTriggers).toEqual(
|
||||
expect.arrayContaining([
|
||||
"code change sync",
|
||||
"route ownership repair",
|
||||
"truth validation or checking",
|
||||
"machine-readable agent context",
|
||||
]),
|
||||
);
|
||||
expect(workflow.allowedWrites).toEqual(["fixed 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);
|
||||
|
||||
expect(description).toMatch(/^Use (automatically|when)/);
|
||||
expect(description).toContain("Not for");
|
||||
expect(description.split(/\s+/u).length).toBeLessThanOrEqual(50);
|
||||
expect(description).not.toContain("Report ");
|
||||
expect(description).not.toContain("Workflow:");
|
||||
}
|
||||
});
|
||||
});
|
||||
+98
-16
@@ -7,7 +7,6 @@ import { runInit } from "../../src/init/init.js";
|
||||
import { runCheck } from "../../src/checks/check.js";
|
||||
import type { TruthHealthScorecard } from "../../src/checks/scorecard.js";
|
||||
import { runConfig } from "../../src/config/command.js";
|
||||
import { TRUTHMARK_VERSION } from "../../src/version.js";
|
||||
import { createTempRepo } from "../helpers/temp-repo.js";
|
||||
|
||||
const initializeRepo = async (rootDir: string): Promise<void> => {
|
||||
@@ -89,6 +88,88 @@ describe("runCheck", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("reports missing host skill package support files", async () => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
try {
|
||||
await initializeRepo(repo.rootDir);
|
||||
await fs.rm(
|
||||
`${repo.rootDir}/.agents/skills/truthmark-sync/support/procedure.md`,
|
||||
);
|
||||
|
||||
const result = await runCheck(repo.rootDir);
|
||||
|
||||
expect(
|
||||
result.diagnostics.some(
|
||||
(diagnostic) =>
|
||||
diagnostic.category === "generated-surface" &&
|
||||
diagnostic.file ===
|
||||
".agents/skills/truthmark-sync/support/procedure.md" &&
|
||||
diagnostic.message.includes("is missing"),
|
||||
),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("reports stale host skill package entrypoints", async () => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
try {
|
||||
await initializeRepo(repo.rootDir);
|
||||
await repo.writeFile(
|
||||
".agents/skills/truthmark-sync/SKILL.md",
|
||||
`${await repo.readFile(".agents/skills/truthmark-sync/SKILL.md")}
|
||||
Local stale edit.
|
||||
`,
|
||||
);
|
||||
|
||||
const result = await runCheck(repo.rootDir);
|
||||
|
||||
expect(
|
||||
result.diagnostics.some(
|
||||
(diagnostic) =>
|
||||
diagnostic.category === "generated-surface" &&
|
||||
diagnostic.file === ".agents/skills/truthmark-sync/SKILL.md" &&
|
||||
diagnostic.message.includes("is stale"),
|
||||
),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("reports stale host skill package support files", async () => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
try {
|
||||
await initializeRepo(repo.rootDir);
|
||||
await repo.writeFile(
|
||||
".claude/skills/truthmark-sync/support/procedure.md",
|
||||
`${await repo.readFile(
|
||||
".claude/skills/truthmark-sync/support/procedure.md",
|
||||
)}
|
||||
Local stale edit.
|
||||
`,
|
||||
);
|
||||
|
||||
const result = await runCheck(repo.rootDir);
|
||||
|
||||
expect(
|
||||
result.diagnostics.some(
|
||||
(diagnostic) =>
|
||||
diagnostic.category === "generated-surface" &&
|
||||
diagnostic.file ===
|
||||
".claude/skills/truthmark-sync/support/procedure.md" &&
|
||||
diagnostic.message.includes("is stale"),
|
||||
),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("returns links diagnostics for broken internal markdown links", async () => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
@@ -96,8 +177,8 @@ describe("runCheck", () => {
|
||||
await runConfig(repo.rootDir, {});
|
||||
await initializeRepo(repo.rootDir);
|
||||
await repo.writeFile(
|
||||
"docs/truthmark/engineering/repository/overview.md",
|
||||
`${await repo.readFile("docs/truthmark/engineering/repository/overview.md")}\nSee [Missing](docs/missing.md).\n`,
|
||||
"docs/truthmark/engineering/repository/bootstrap-routing.md",
|
||||
`${await repo.readFile("docs/truthmark/engineering/repository/bootstrap-routing.md")}\nSee [Missing](docs/missing.md).\n`,
|
||||
);
|
||||
|
||||
const result = await runCheck(repo.rootDir);
|
||||
@@ -127,8 +208,8 @@ describe("runCheck", () => {
|
||||
"utf8",
|
||||
);
|
||||
await repo.writeFile(
|
||||
"docs/truthmark/engineering/repository/overview.md",
|
||||
`${await repo.readFile("docs/truthmark/engineering/repository/overview.md")}\nSee [Outside](../truthmark-outside-link.md).\n`,
|
||||
"docs/truthmark/engineering/repository/bootstrap-routing.md",
|
||||
`${await repo.readFile("docs/truthmark/engineering/repository/bootstrap-routing.md")}\nSee [Outside](../truthmark-outside-link.md).\n`,
|
||||
);
|
||||
|
||||
const result = await runCheck(repo.rootDir);
|
||||
@@ -138,7 +219,7 @@ describe("runCheck", () => {
|
||||
(diagnostic) =>
|
||||
diagnostic.category === "links" &&
|
||||
diagnostic.file ===
|
||||
"docs/truthmark/engineering/repository/overview.md",
|
||||
"docs/truthmark/engineering/repository/bootstrap-routing.md",
|
||||
),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
@@ -169,8 +250,8 @@ describe("runCheck", () => {
|
||||
path.resolve(repo.rootDir, "docs", "linked-outside.md"),
|
||||
);
|
||||
await repo.writeFile(
|
||||
"docs/truthmark/engineering/repository/overview.md",
|
||||
`${await repo.readFile("docs/truthmark/engineering/repository/overview.md")}\nSee [Outside](docs/linked-outside.md).\n`,
|
||||
"docs/truthmark/engineering/repository/bootstrap-routing.md",
|
||||
`${await repo.readFile("docs/truthmark/engineering/repository/bootstrap-routing.md")}\nSee [Outside](docs/linked-outside.md).\n`,
|
||||
);
|
||||
|
||||
const result = await runCheck(repo.rootDir);
|
||||
@@ -180,7 +261,7 @@ describe("runCheck", () => {
|
||||
(diagnostic) =>
|
||||
diagnostic.category === "links" &&
|
||||
diagnostic.file ===
|
||||
"docs/truthmark/engineering/repository/overview.md",
|
||||
"docs/truthmark/engineering/repository/bootstrap-routing.md",
|
||||
),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
@@ -662,18 +743,16 @@ Update truth when:
|
||||
}
|
||||
});
|
||||
|
||||
it("reports stale generated workflow surfaces and version mismatches", async () => {
|
||||
it("reports stale generated workflow surfaces by comparing rendered content", async () => {
|
||||
const repo = await createTempRepo();
|
||||
|
||||
try {
|
||||
await initializeRepo(repo.rootDir);
|
||||
await repo.writeFile(
|
||||
".agents/skills/truthmark-sync/SKILL.md",
|
||||
`${(
|
||||
await repo.readFile(".agents/skills/truthmark-sync/SKILL.md")
|
||||
).replace(
|
||||
`truthmark-version: ${TRUTHMARK_VERSION}`,
|
||||
"truthmark-version: 0.9.0",
|
||||
`${(await repo.readFile(".agents/skills/truthmark-sync/SKILL.md")).replace(
|
||||
"Truthmark-managed generated file.",
|
||||
"Locally edited generated file.",
|
||||
)}\n`,
|
||||
);
|
||||
const result = await runCheck(repo.rootDir);
|
||||
@@ -686,9 +765,12 @@ Update truth when:
|
||||
file: ".agents/skills/truthmark-sync/SKILL.md",
|
||||
message: expect.stringContaining("stale"),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(result.diagnostics).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
category: "generated-surface",
|
||||
severity: "review",
|
||||
file: ".agents/skills/truthmark-sync/SKILL.md",
|
||||
message: expect.stringContaining("version"),
|
||||
}),
|
||||
|
||||
@@ -86,12 +86,26 @@ describe("repository intelligence CLI commands", () => {
|
||||
data: {
|
||||
workflowState: {
|
||||
applicability: { state: string; reasons: string[] };
|
||||
actionContext: { allowedWritePaths: string[] };
|
||||
actionContext: {
|
||||
allowedWritePaths: string[];
|
||||
primaryTruthDocs: string[];
|
||||
evidencePrompts: string[];
|
||||
requiredEvidence?: unknown;
|
||||
};
|
||||
targetTruthDocs: string[];
|
||||
changedFiles: Array<{ path: string }>;
|
||||
affectedRoutes: unknown[];
|
||||
workflowCard: {
|
||||
affectedFiles: string[];
|
||||
likelyRouteOwners: string[];
|
||||
suggestedTruthDocs: string[];
|
||||
openQuestions: string[];
|
||||
skippedHelperStatus: Array<{ helper: string; status: string; reason: string }>;
|
||||
};
|
||||
checks: {
|
||||
required: string[];
|
||||
reviewChecklist: string[];
|
||||
reviewQuestions?: unknown;
|
||||
required?: unknown;
|
||||
recommended: string[];
|
||||
helpers: unknown[];
|
||||
affectedTests?: string[];
|
||||
@@ -110,17 +124,33 @@ describe("repository intelligence CLI commands", () => {
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(output.command).toBe("workflow status");
|
||||
expect(state.applicability.state).toBe("applicable");
|
||||
expect(state.applicability.state).toBe("ready");
|
||||
expect(state.applicability.reasons).toEqual([]);
|
||||
expect(state.actionContext.allowedWritePaths).toEqual(
|
||||
expect.arrayContaining(state.targetTruthDocs),
|
||||
);
|
||||
expect(state.actionContext.primaryTruthDocs).toEqual(
|
||||
state.targetTruthDocs,
|
||||
);
|
||||
expect(state.actionContext.allowedWritePaths).toContain("docs/truthmark/routes/areas.md");
|
||||
expect(state.targetTruthDocs.length).toBeGreaterThan(0);
|
||||
expect(state.changedFiles.map((file) => file.path)).toContain("src/math.ts");
|
||||
expect(state.affectedRoutes.length).toBeGreaterThan(0);
|
||||
expect(state.checks.required.length).toBeGreaterThan(0);
|
||||
expect(state.actionContext.evidencePrompts.length).toBeGreaterThan(0);
|
||||
expect(state.actionContext.requiredEvidence).toBeUndefined();
|
||||
expect(state.checks.reviewChecklist.length).toBeGreaterThan(0);
|
||||
expect(state.checks.reviewQuestions).toBeUndefined();
|
||||
expect(state.checks.required).toBeUndefined();
|
||||
expect(state.checks.helpers.length).toBeGreaterThan(0);
|
||||
expect(state.workflowCard.affectedFiles).toContain("src/math.ts");
|
||||
expect(state.workflowCard.likelyRouteOwners.length).toBeGreaterThan(0);
|
||||
expect(state.workflowCard.suggestedTruthDocs).toEqual(state.targetTruthDocs);
|
||||
expect(state.workflowCard.openQuestions).toEqual([]);
|
||||
expect(state.workflowCard.skippedHelperStatus).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ helper: "validate-sync-report", status: "skipped" }),
|
||||
]),
|
||||
);
|
||||
expect(Array.isArray(state.nextSteps)).toBe(true);
|
||||
expect(Array.isArray(state.diagnostics)).toBe(true);
|
||||
expect(compactTestGuidance.join("\n")).toContain("tests/math.test.ts");
|
||||
|
||||
@@ -14,6 +14,9 @@ Ownership reviewed:
|
||||
Truth docs updated:
|
||||
- docs/truthmark/truth/init-and-scaffold.md
|
||||
|
||||
Decision/rationale captured:
|
||||
- none provided in task conversation
|
||||
|
||||
Evidence checked:
|
||||
- Claim: Init writes generated workflow files.
|
||||
Evidence: src/init/init.ts
|
||||
@@ -27,6 +30,21 @@ Notes:
|
||||
- Complete.
|
||||
`;
|
||||
|
||||
const validSyncReportWithIntent = validSyncReport.replace(
|
||||
"Ownership reviewed:",
|
||||
`Sync Intent:
|
||||
- Changed code reviewed: src/init/init.ts
|
||||
- Affected route/truth owner: docs/truthmark/routes/areas.md
|
||||
- Target truth docs: docs/truthmark/truth/init-and-scaffold.md
|
||||
- Intended update: Update init workflow truth.
|
||||
- Evidence to verify: src/init/init.ts
|
||||
- User-provided decisions/rationale: none provided in task conversation
|
||||
- No-update-needed rationale: not applicable; mapped truth is stale
|
||||
- Blockers: none
|
||||
|
||||
Ownership reviewed:`,
|
||||
);
|
||||
|
||||
const validDocumentReport = `Truth Document: completed
|
||||
|
||||
Implementation reviewed:
|
||||
@@ -88,6 +106,26 @@ describe("truthmark validate CLI helpers", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("validates sync reports that include optional Sync Intent", async () => {
|
||||
const repo = await createTempRepo();
|
||||
try {
|
||||
await repo.writeFile("report.md", validSyncReportWithIntent);
|
||||
|
||||
const result = await runCli(["validate", "sync-report", "report.md", "--json"], {
|
||||
cwd: repo.rootDir,
|
||||
});
|
||||
const output = JSON.parse(result.stdout) as {
|
||||
data?: { validation?: { ok: boolean; checks?: string[] } };
|
||||
};
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(output.data?.validation?.ok).toBe(true);
|
||||
expect(output.data?.validation?.checks).toContain("Sync Intent");
|
||||
} finally {
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("validates document reports through the Truthmark CLI", async () => {
|
||||
const repo = await createTempRepo();
|
||||
try {
|
||||
|
||||
@@ -1,525 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createDefaultConfig } from "../../src/config/defaults.js";
|
||||
import { renderGeneratedSurfaces } from "../../src/templates/generated-surfaces.js";
|
||||
import {
|
||||
TRUTHMARK_WORKFLOW_IDS,
|
||||
TRUTHMARK_WORKFLOW_MANIFEST,
|
||||
getTruthmarkWorkflow,
|
||||
type TruthmarkWorkflowId,
|
||||
} from "../../src/agents/workflow-manifest.js";
|
||||
import { NO_CLI_FALLBACK_EVAL_CASES } from "./no-cli-fallback-cases.js";
|
||||
import { WORKFLOW_ROUTING_EVAL_CASES } from "./workflow-routing-cases.js";
|
||||
|
||||
const WORKFLOW_COMMAND_PATHS: Record<TruthmarkWorkflowId, string> = {
|
||||
"truthmark-sync": ".gemini/commands/truthmark/sync.toml",
|
||||
"truthmark-structure": ".gemini/commands/truthmark/structure.toml",
|
||||
"truthmark-document": ".gemini/commands/truthmark/document.toml",
|
||||
"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[] => [
|
||||
`.agents/skills/${id}/SKILL.md`,
|
||||
`.opencode/skills/${id}/SKILL.md`,
|
||||
`.claude/skills/${id}/SKILL.md`,
|
||||
`.github/prompts/${id}.prompt.md`,
|
||||
WORKFLOW_COMMAND_PATHS[id],
|
||||
];
|
||||
|
||||
const CODEX_METADATA_PATHS = (id: TruthmarkWorkflowId): string =>
|
||||
`.agents/skills/${id}/agents/openai.yaml`;
|
||||
|
||||
const WORKFLOW_SKILL_PACKAGE_PATHS = (
|
||||
hostSkillRoot: string,
|
||||
id: TruthmarkWorkflowId,
|
||||
): readonly string[] => {
|
||||
const workflow = TRUTHMARK_WORKFLOW_MANIFEST[id];
|
||||
const hasSubagentSupport =
|
||||
("subagents" in workflow ? workflow.subagents.length : 0) > 0 ||
|
||||
("writeSubagents" in workflow ? workflow.writeSubagents.length : 0) > 0;
|
||||
|
||||
return [
|
||||
`${hostSkillRoot}/${id}/SKILL.md`,
|
||||
`${hostSkillRoot}/${id}/support/procedure.md`,
|
||||
`${hostSkillRoot}/${id}/support/report-template.md`,
|
||||
...(hasSubagentSupport
|
||||
? [`${hostSkillRoot}/${id}/support/subagents-and-leases.md`]
|
||||
: []),
|
||||
];
|
||||
};
|
||||
|
||||
const WORKFLOW_CONTRACT_PATH_GROUPS = (
|
||||
id: TruthmarkWorkflowId,
|
||||
): readonly (readonly string[])[] => [
|
||||
WORKFLOW_SKILL_PACKAGE_PATHS(".agents/skills", id),
|
||||
WORKFLOW_SKILL_PACKAGE_PATHS(".opencode/skills", id),
|
||||
WORKFLOW_SKILL_PACKAGE_PATHS(".claude/skills", id),
|
||||
];
|
||||
|
||||
const WORKFLOW_ADAPTER_PATHS = (id: TruthmarkWorkflowId): readonly string[] => [
|
||||
`.github/prompts/${id}.prompt.md`,
|
||||
WORKFLOW_COMMAND_PATHS[id],
|
||||
];
|
||||
|
||||
const GENERATED_SKILL_SURFACE_PATTERN = /\/SKILL\.md$/u;
|
||||
const GENERATED_COMMAND_ADAPTER_PATTERN =
|
||||
/^\.github\/prompts\/truthmark-[^/]+\.prompt\.md$|^\.gemini\/commands\/truthmark\/[^/]+\.toml$/u;
|
||||
const SURFACE_TOKEN_PATTERN = /\S+/gu;
|
||||
const ADAPTER_NEXT_STEP_SELF_INVOCATION_PATTERN =
|
||||
/\b(?:run|invoke|open|use|call|execute|dispatch|start)\s+(?:the\s+)?\/?\$?truthmark[-:\s][a-z-]+\b/iu;
|
||||
|
||||
const surfaceTokenCount = (content: string): number =>
|
||||
content.match(SURFACE_TOKEN_PATTERN)?.length ?? 0;
|
||||
|
||||
const surfaceLineCount = (content: string): number =>
|
||||
content.split(/\r?\n/u).length;
|
||||
|
||||
const nonProhibitionLinesMatching = (
|
||||
content: string,
|
||||
pattern: RegExp,
|
||||
): string[] =>
|
||||
content
|
||||
.split(/\r?\n/u)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => pattern.test(line))
|
||||
.filter(
|
||||
(line) => !/\b(?:do not|must not|never|none|without)\b/iu.test(line),
|
||||
);
|
||||
|
||||
const SURFACE_CONTRACT_TERMS: Record<TruthmarkWorkflowId, readonly string[]> = {
|
||||
"truthmark-sync": [
|
||||
"Skip docs-only",
|
||||
"block and recommend Truth Structure",
|
||||
"verify only truth docs and leased truth routing files changed",
|
||||
"Report completion in this shape:",
|
||||
"Changed code reviewed:",
|
||||
"Evidence checked:",
|
||||
],
|
||||
"truthmark-structure": [
|
||||
"New area setup",
|
||||
"report the initial truth boundary",
|
||||
"create or repair",
|
||||
"starter truth docs",
|
||||
"Do not require the truthmark CLI",
|
||||
"Report completion in this shape:",
|
||||
"Topology reviewed:",
|
||||
"Initial truth boundary:",
|
||||
"Evidence checked:",
|
||||
],
|
||||
"truthmark-document": [
|
||||
"document existing implemented behavior",
|
||||
"Not for functional-code changes",
|
||||
"topology repair that needs Structure",
|
||||
],
|
||||
"truthmark-preview": [
|
||||
"preview Truthmark routing",
|
||||
"Truth Preview is read-only",
|
||||
"Report completion in this shape:",
|
||||
"Truth Preview: completed",
|
||||
"Handoff:",
|
||||
],
|
||||
"truthmark-realize": [
|
||||
"Use this skill only when the user explicitly asks",
|
||||
"may write functional code only",
|
||||
"must not edit truth docs or truth routing",
|
||||
"Report completion in this shape:",
|
||||
"Truth docs used:",
|
||||
"Verification:",
|
||||
],
|
||||
"truthmark-check": [
|
||||
"audit repository truth health",
|
||||
"report issues and suggested fixes without silently rewriting unrelated files",
|
||||
"Report completion in this shape:",
|
||||
"Files reviewed:",
|
||||
"Validation:",
|
||||
],
|
||||
"truthmark-portal": [
|
||||
"manual-only presentation workflow",
|
||||
"fixed Portal output directory only",
|
||||
"Markdown remains canonical",
|
||||
"Truthmark Portal: completed",
|
||||
"Output path:",
|
||||
"Markdown canonical statement:",
|
||||
],
|
||||
};
|
||||
|
||||
const NO_CLI_FALLBACK_SCENARIOS = [
|
||||
"single-file-one-truth-doc",
|
||||
"multi-file-one-truth-doc",
|
||||
"multi-route-multiple-truth-docs",
|
||||
"ambiguous-unmapped-code-blocks",
|
||||
"broad-index-truth-doc-triggers-structure",
|
||||
"evidence-reference-stale-or-missing",
|
||||
"preserve-product-decisions-rationale",
|
||||
] as const;
|
||||
|
||||
const NO_CLI_FALLBACK_EQUIVALENCE_AXES = [
|
||||
"same target docs",
|
||||
"same block/apply decision",
|
||||
"same write boundary",
|
||||
"same evidence status",
|
||||
] as const;
|
||||
|
||||
const manifestRoutingText = (id: TruthmarkWorkflowId): string => {
|
||||
const workflow = getTruthmarkWorkflow(id);
|
||||
|
||||
return [
|
||||
workflow.description,
|
||||
workflow.defaultPrompt,
|
||||
...workflow.positiveTriggers,
|
||||
...workflow.negativeTriggers,
|
||||
...workflow.forbiddenAdjacency,
|
||||
].join("\n");
|
||||
};
|
||||
|
||||
const candidateManifestText = (
|
||||
expectedWorkflow: TruthmarkWorkflowId | "none" | "block",
|
||||
forbiddenWorkflows: readonly TruthmarkWorkflowId[] = [],
|
||||
): string => {
|
||||
const workflowIds =
|
||||
expectedWorkflow === "none" || expectedWorkflow === "block"
|
||||
? forbiddenWorkflows
|
||||
: [expectedWorkflow, ...forbiddenWorkflows];
|
||||
|
||||
return workflowIds.map((id) => manifestRoutingText(id)).join("\n");
|
||||
};
|
||||
|
||||
const buildGeneratedSurfaceMap = (): Map<string, string> => {
|
||||
const config = createDefaultConfig();
|
||||
config.truthmark.generated.portal.enabled = true;
|
||||
return new Map(
|
||||
renderGeneratedSurfaces(config).map((surface) => [
|
||||
surface.path,
|
||||
surface.content,
|
||||
]),
|
||||
);
|
||||
};
|
||||
|
||||
describe("workflow routing eval corpus", () => {
|
||||
it("covers positive, negative, adjacent, none, and blocked routing outcomes", () => {
|
||||
expect(WORKFLOW_ROUTING_EVAL_CASES).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ source: "manifest-positive" }),
|
||||
expect.objectContaining({ source: "manifest-negative" }),
|
||||
expect.objectContaining({ source: "forbidden-adjacent" }),
|
||||
expect.objectContaining({ expectedWorkflow: "none" }),
|
||||
expect.objectContaining({ expectedWorkflow: "block" }),
|
||||
]),
|
||||
);
|
||||
|
||||
for (const id of TRUTHMARK_WORKFLOW_IDS) {
|
||||
expect(WORKFLOW_ROUTING_EVAL_CASES).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ expectedWorkflow: id }),
|
||||
]),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it.each(WORKFLOW_ROUTING_EVAL_CASES)(
|
||||
"anchors $id to manifest routing signals",
|
||||
(testCase) => {
|
||||
const routingText = candidateManifestText(
|
||||
testCase.expectedWorkflow,
|
||||
testCase.forbiddenWorkflows,
|
||||
);
|
||||
|
||||
for (const signal of testCase.expectedManifestSignals) {
|
||||
expect(routingText).toContain(signal);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("no-CLI fallback eval corpus", () => {
|
||||
it("covers route-first fallback outcomes before any hard budget policy is added", () => {
|
||||
const caseIds = NO_CLI_FALLBACK_EVAL_CASES.map((testCase) => testCase.id);
|
||||
|
||||
expect(caseIds).toEqual(
|
||||
expect.arrayContaining([...NO_CLI_FALLBACK_SCENARIOS]),
|
||||
);
|
||||
|
||||
for (const testCase of NO_CLI_FALLBACK_EVAL_CASES) {
|
||||
expect(testCase.changedSurface.length).toBeGreaterThan(0);
|
||||
expect(testCase.equivalenceAxes).toEqual([
|
||||
...NO_CLI_FALLBACK_EQUIVALENCE_AXES,
|
||||
]);
|
||||
expect(testCase.expectedCliOutcome).toEqual(
|
||||
expect.objectContaining({
|
||||
decision: expect.stringMatching(/^(apply|block|structure)$/u),
|
||||
evidenceStatus: expect.stringMatching(
|
||||
/^(current|missing-or-stale|requires-preservation-check)$/u,
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
expect(
|
||||
NO_CLI_FALLBACK_EVAL_CASES.map((testCase) => testCase.scenario).join(
|
||||
"\n",
|
||||
),
|
||||
).not.toMatch(/token budget|hard budget/iu);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generated workflow surface conformance", () => {
|
||||
const surfaces = buildGeneratedSurfaceMap();
|
||||
|
||||
it("renders every workflow routing surface from the manifest description", () => {
|
||||
for (const id of TRUTHMARK_WORKFLOW_IDS) {
|
||||
const workflow = TRUTHMARK_WORKFLOW_MANIFEST[id];
|
||||
|
||||
for (const path of WORKFLOW_SURFACE_PATHS(id)) {
|
||||
const content = surfaces.get(path);
|
||||
|
||||
expect(content, `${path} is generated`).toBeDefined();
|
||||
expect(content).toContain(workflow.description);
|
||||
expect(content).toContain(workflow.description.split("Not for ")[1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps Codex metadata aligned with manifest selection policy", () => {
|
||||
for (const id of TRUTHMARK_WORKFLOW_IDS) {
|
||||
const workflow = TRUTHMARK_WORKFLOW_MANIFEST[id];
|
||||
const path = CODEX_METADATA_PATHS(id);
|
||||
const content = surfaces.get(path);
|
||||
|
||||
expect(content, `${path} is generated`).toBeDefined();
|
||||
expect(content).toContain(`display_name: "${workflow.displayName}"`);
|
||||
expect(content).toContain(
|
||||
`short_description: "${workflow.shortDescription}"`,
|
||||
);
|
||||
expect(content).toContain(`default_prompt: "${workflow.defaultPrompt}"`);
|
||||
expect(content).toContain(
|
||||
`allow_implicit_invocation: ${workflow.allowImplicitInvocation}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps generated skill, prompt, and command bodies on the same write and report contract", () => {
|
||||
for (const id of TRUTHMARK_WORKFLOW_IDS) {
|
||||
for (const paths of WORKFLOW_CONTRACT_PATH_GROUPS(id)) {
|
||||
const content = paths
|
||||
.map((path) => {
|
||||
const surface = surfaces.get(path);
|
||||
|
||||
expect(surface, `${path} is generated`).toBeDefined();
|
||||
return surface;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
for (const term of SURFACE_CONTRACT_TERMS[id]) {
|
||||
expect(content).toContain(term);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps prompt and command adapters linked to canonical skill packages", () => {
|
||||
for (const id of TRUTHMARK_WORKFLOW_IDS) {
|
||||
const workflow = getTruthmarkWorkflow(id);
|
||||
|
||||
for (const path of WORKFLOW_ADAPTER_PATHS(id)) {
|
||||
const content = surfaces.get(path);
|
||||
|
||||
expect(content, `${path} is generated`).toBeDefined();
|
||||
expect(content).toContain(workflow.description);
|
||||
expect(content).toContain("entrypoint for");
|
||||
expect(content).toContain(
|
||||
"Do not invoke another Truthmark command from here.",
|
||||
);
|
||||
expect(content).toContain(
|
||||
"If skill entrypoints are unavailable, use the host's direct evidence-first manual fallback procedure.",
|
||||
);
|
||||
expect(content).toContain(`${id}/SKILL.md`);
|
||||
expect(content).toContain(`${id}/support/procedure.md`);
|
||||
expect(content).toContain(`${id}/support/report-template.md`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps read-only generated surfaces free of write-authorizing guidance", () => {
|
||||
const readOnlySurfacePaths = Array.from(surfaces.keys()).filter(
|
||||
(path) =>
|
||||
path.includes("truthmark-preview") ||
|
||||
path.includes("truthmark-check") ||
|
||||
path.includes("route-auditor") ||
|
||||
path.includes("claim-verifier") ||
|
||||
path.includes("doc-reviewer"),
|
||||
);
|
||||
const writeAuthorityPatterns = [
|
||||
/\bbefore writing\b/iu,
|
||||
/\bmay write\b/iu,
|
||||
/\bwrite canonical truth docs\b/iu,
|
||||
/\bwrite lease\b/iu,
|
||||
];
|
||||
|
||||
expect(readOnlySurfacePaths.length).toBeGreaterThan(0);
|
||||
for (const path of readOnlySurfacePaths) {
|
||||
const content = surfaces.get(path) ?? "";
|
||||
|
||||
for (const pattern of writeAuthorityPatterns) {
|
||||
expect(nonProhibitionLinesMatching(content, pattern), path).toEqual([]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps Truth Realize surfaces from inheriting truth-doc write instructions", () => {
|
||||
const realizeSurfacePaths = Array.from(surfaces.keys()).filter(
|
||||
(path) =>
|
||||
path.includes("truthmark-realize") &&
|
||||
(path.endsWith("/SKILL.md") ||
|
||||
path.endsWith("/support/procedure.md") ||
|
||||
path.startsWith(".github/prompts/") ||
|
||||
path.startsWith(".gemini/commands/")),
|
||||
);
|
||||
|
||||
expect(realizeSurfacePaths.length).toBeGreaterThan(0);
|
||||
for (const path of realizeSurfacePaths) {
|
||||
const content = surfaces.get(path) ?? "";
|
||||
|
||||
expect(
|
||||
nonProhibitionLinesMatching(
|
||||
content,
|
||||
/\b(?:write|edit|update|patch|create)\b.*\b(?:truth docs?|truth routing|canonical truth docs?)\b/iu,
|
||||
),
|
||||
path,
|
||||
).toEqual([]);
|
||||
if (
|
||||
path.endsWith("/SKILL.md") ||
|
||||
path.endsWith("/support/procedure.md")
|
||||
) {
|
||||
expect(content, path).toMatch(
|
||||
/\b(?:do not|must not|never)\b.*\b(?:edit|write|update)\b.*\b(?:truth docs?|truth routing)\b/iu,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps host command adapters from self-recursive next steps", () => {
|
||||
for (const id of TRUTHMARK_WORKFLOW_IDS) {
|
||||
for (const path of WORKFLOW_ADAPTER_PATHS(id)) {
|
||||
const content = surfaces.get(path);
|
||||
|
||||
expect(content, `${path} is generated`).toBeDefined();
|
||||
expect(content, path).not.toMatch(
|
||||
ADAPTER_NEXT_STEP_SELF_INVOCATION_PATTERN,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps generated skills and command adapters under deterministic size ceilings", () => {
|
||||
const ceilings = [
|
||||
{
|
||||
pattern: GENERATED_SKILL_SURFACE_PATTERN,
|
||||
maxLines: 45,
|
||||
maxTokens: 350,
|
||||
},
|
||||
{
|
||||
pattern: GENERATED_COMMAND_ADAPTER_PATTERN,
|
||||
maxLines: 25,
|
||||
maxTokens: 125,
|
||||
},
|
||||
];
|
||||
|
||||
for (const [path, content] of surfaces) {
|
||||
const ceiling = ceilings.find(({ pattern }) => pattern.test(path));
|
||||
|
||||
if (ceiling === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
expect(
|
||||
surfaceLineCount(content),
|
||||
`${path} line count`,
|
||||
).toBeLessThanOrEqual(ceiling.maxLines);
|
||||
expect(
|
||||
surfaceTokenCount(content),
|
||||
`${path} token count`,
|
||||
).toBeLessThanOrEqual(ceiling.maxTokens);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps write-workflow no-CLI fallback route-first and non-expansive", () => {
|
||||
const writeWorkflowExpectations: Record<
|
||||
| "truthmark-sync"
|
||||
| "truthmark-structure"
|
||||
| "truthmark-document"
|
||||
| "truthmark-realize",
|
||||
readonly string[]
|
||||
> = {
|
||||
"truthmark-sync": [
|
||||
"Inspect .truthmark/config.yml and configured route files",
|
||||
"only when they exist; then inspect relevant canonical docs directly.",
|
||||
"direct checkout inspection is the canonical path; do not require the truthmark binary.",
|
||||
"May write canonical truth docs and truth routing files only; must not rewrite functional code.",
|
||||
"Read support/procedure.md before editing truth docs.",
|
||||
],
|
||||
"truthmark-structure": [
|
||||
"Inspect .truthmark/config.yml and configured route files",
|
||||
"only when they exist; then inspect current docs and relevant code directly.",
|
||||
"Define areas by product or behavior ownership, not by mechanical directory mirroring.",
|
||||
"Do not edit functional code.",
|
||||
"Read support/procedure.md before writing route or starter truth-doc changes.",
|
||||
],
|
||||
"truthmark-document": [
|
||||
"Inspect .truthmark/config.yml and configured route files",
|
||||
"only when they exist; then inspect existing canonical docs, implementation code, and tests directly.",
|
||||
"Document current implemented behavior; do not invent future behavior.",
|
||||
"May write canonical truth docs and truth routing files only; must not write functional code.",
|
||||
"Read support/procedure.md before editing truth docs.",
|
||||
],
|
||||
"truthmark-realize": [
|
||||
"Read the source truth docs, inspect .truthmark/config.yml and configured route files",
|
||||
"only when they exist, then inspect tests and relevant functional code directly.",
|
||||
"Truth docs lead; code follows.",
|
||||
"may write functional code only; must not edit truth docs or truth routing while realizing those docs.",
|
||||
"Read support/procedure.md before changing code.",
|
||||
],
|
||||
};
|
||||
|
||||
for (const [id, expectedTerms] of Object.entries(
|
||||
writeWorkflowExpectations,
|
||||
)) {
|
||||
const content = surfaces.get(`.agents/skills/${id}/SKILL.md`);
|
||||
|
||||
expect(content, `${id} Codex skill is generated`).toBeDefined();
|
||||
expect(content).toContain(
|
||||
"Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
|
||||
);
|
||||
expect(content).not.toContain("## Optional local CLI validation");
|
||||
expect(content).not.toContain(
|
||||
"If the local Truthmark CLI is unavailable or too old",
|
||||
);
|
||||
expect(content).not.toContain(
|
||||
"use the checked-in workflow files as the contract",
|
||||
);
|
||||
expect(content).not.toContain("Follow the route-first procedure");
|
||||
|
||||
for (const term of expectedTerms) {
|
||||
expect(content).toContain(term);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("labels non-main progressive-disclosure files as conditional", () => {
|
||||
const syncSkill = surfaces.get(".agents/skills/truthmark-sync/SKILL.md");
|
||||
|
||||
expect(syncSkill).toContain(
|
||||
"support/procedure.md — read before edits or detailed auditing; contains core quality gates",
|
||||
);
|
||||
expect(syncSkill).toContain(
|
||||
"support/subagents-and-leases.md — read only when using subagents, leases, or accepting worker output",
|
||||
);
|
||||
expect(syncSkill).toContain(
|
||||
"helper-manifest.yml — read only when invoking helper validators or validating helper registration",
|
||||
);
|
||||
expect(syncSkill).toContain(
|
||||
"support/helper-policy.md — read only when invoking helper validators or reporting helper status",
|
||||
);
|
||||
});
|
||||
});
|
||||
+40
-13
@@ -15,8 +15,14 @@ describe("buildImpactSet", () => {
|
||||
it("maps changed files to routes, truth docs, and tests", async () => {
|
||||
const repo = await createTempRepo();
|
||||
repos.push(repo);
|
||||
await repo.writeFile("src/math.ts", "export function add(left: number, right: number) { return left + right; }\n");
|
||||
await repo.writeFile("tests/math.test.ts", "import { add } from '../src/math.js';\n");
|
||||
await repo.writeFile(
|
||||
"src/math.ts",
|
||||
"export function add(left: number, right: number) { return left + right; }\n",
|
||||
);
|
||||
await repo.writeFile(
|
||||
"tests/math.test.ts",
|
||||
"import { add } from '../src/math.js';\n",
|
||||
);
|
||||
await runConfig(repo.rootDir, { force: false, stdout: false });
|
||||
await runInit(repo.rootDir);
|
||||
await repo.runGit(["add", "."]);
|
||||
@@ -30,7 +36,9 @@ describe("buildImpactSet", () => {
|
||||
const impact = await buildImpactSet(repo.rootDir, { base: "main" });
|
||||
|
||||
expect(impact.schemaVersion).toBe("impact-set/v0");
|
||||
expect(impact.changedFiles).toContainEqual(expect.objectContaining({ path: "src/math.ts", status: "modified" }));
|
||||
expect(impact.changedFiles).toContainEqual(
|
||||
expect.objectContaining({ path: "src/math.ts", status: "modified" }),
|
||||
);
|
||||
expect(impact.affectedTruthDocs.length).toBeGreaterThan(0);
|
||||
expect(impact.affectedTests).toContain("tests/math.test.ts");
|
||||
expect(impact).not.toHaveProperty("changedPublicSymbols");
|
||||
@@ -45,7 +53,8 @@ describe("buildImpactSet", () => {
|
||||
await repo.runGit(["add", "."]);
|
||||
await repo.runGit(["commit", "-m", "initial"]);
|
||||
|
||||
const truthDocPath = "docs/truthmark/engineering/repository/overview.md";
|
||||
const truthDocPath =
|
||||
"docs/truthmark/engineering/repository/bootstrap-routing.md";
|
||||
await repo.writeFile(
|
||||
truthDocPath,
|
||||
`${await repo.readFile(truthDocPath)}\nUpdated direct truth-doc edit.\n`,
|
||||
@@ -57,9 +66,11 @@ describe("buildImpactSet", () => {
|
||||
expect.objectContaining({ path: truthDocPath, status: "modified" }),
|
||||
);
|
||||
expect(impact.affectedTruthDocs).toContain(truthDocPath);
|
||||
expect(impact.affectedRoutes.some((route) => route.truthDocs.includes(truthDocPath))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
impact.affectedRoutes.some((route) =>
|
||||
route.truthDocs.includes(truthDocPath),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("reports when a base ref cannot be compared", async () => {
|
||||
@@ -75,7 +86,9 @@ describe("buildImpactSet", () => {
|
||||
expect.objectContaining({
|
||||
category: "impact",
|
||||
severity: "error",
|
||||
message: expect.stringContaining("Unable to compare base ref missing-ref"),
|
||||
message: expect.stringContaining(
|
||||
"Unable to compare base ref missing-ref",
|
||||
),
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -99,7 +112,9 @@ describe("buildImpactSet", () => {
|
||||
category: "impact",
|
||||
severity: "review",
|
||||
file: "src/index.ts",
|
||||
message: expect.stringContaining("affected truth docs but none were changed"),
|
||||
message: expect.stringContaining(
|
||||
"affected truth docs but none were changed",
|
||||
),
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -109,12 +124,18 @@ describe("buildImpactSet", () => {
|
||||
repos.push(repo);
|
||||
|
||||
await repo.writeFile("src/index.ts", "export const value = 1;\n");
|
||||
await repo.writeFile("tests/index.test.ts", "import { value } from '../src/index.js';\n");
|
||||
await repo.writeFile(
|
||||
"tests/index.test.ts",
|
||||
"import { value } from '../src/index.js';\n",
|
||||
);
|
||||
await runConfig(repo.rootDir, { force: false, stdout: false });
|
||||
await runInit(repo.rootDir);
|
||||
await repo.runGit(["add", "."]);
|
||||
await repo.runGit(["commit", "-m", "initial"]);
|
||||
await repo.writeFile("tests/index.test.ts", "import { value } from '../src/index.js';\nvoid value;\n");
|
||||
await repo.writeFile(
|
||||
"tests/index.test.ts",
|
||||
"import { value } from '../src/index.js';\nvoid value;\n",
|
||||
);
|
||||
|
||||
const impact = await buildImpactSet(repo.rootDir, { base: "main" });
|
||||
|
||||
@@ -130,7 +151,10 @@ describe("buildImpactSet", () => {
|
||||
it("selects package-level tests for changed package files", async () => {
|
||||
const repo = await createTempRepo();
|
||||
repos.push(repo);
|
||||
await repo.writeFile("src/repo-index/package-metadata.ts", "export const manager = 'npm';\n");
|
||||
await repo.writeFile(
|
||||
"src/repo-index/package-metadata.ts",
|
||||
"export const manager = 'npm';\n",
|
||||
);
|
||||
await repo.writeFile(
|
||||
"tests/repo-index/build.test.ts",
|
||||
"import { describe, it } from 'vitest';\ndescribe('repo index package', () => { it('builds', () => undefined); });\n",
|
||||
@@ -201,7 +225,10 @@ Update truth when:
|
||||
status: "renamed",
|
||||
}),
|
||||
);
|
||||
expect(impact.affectedTruthDocs).toEqual(["docs/truthmark/truth/new.md", "docs/truthmark/truth/old.md"]);
|
||||
expect(impact.affectedTruthDocs).toEqual([
|
||||
"docs/truthmark/truth/new.md",
|
||||
"docs/truthmark/truth/old.md",
|
||||
]);
|
||||
expect(impact).not.toHaveProperty("changedPublicSymbols");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import { runConfig } from "../../src/config/command.js";
|
||||
import { runInit } from "../../src/init/init.js";
|
||||
import { TRUTHMARK_VERSION } from "../../src/version.js";
|
||||
|
||||
import { createTempRepo } from "../helpers/temp-repo.js";
|
||||
|
||||
describe("runInit instruction integration", () => {
|
||||
@@ -23,7 +23,8 @@ describe("runInit instruction integration", () => {
|
||||
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(`Generated by Truthmark ${TRUTHMARK_VERSION}`);
|
||||
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");
|
||||
@@ -80,7 +81,9 @@ ignore: []
|
||||
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 reopen the gate");
|
||||
expect(agents).toContain(
|
||||
"later functional changes need a fresh Sync review",
|
||||
);
|
||||
expect(agents).not.toContain("Explicit invocation:");
|
||||
} finally {
|
||||
await repo.cleanup();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createTempRepo } from "../helpers/temp-repo.js";
|
||||
import { runCli } from "../helpers/run-cli.js";
|
||||
import { TRUTHMARK_VERSION } from "../../src/version.js";
|
||||
|
||||
describe("installed workflow contract", () => {
|
||||
it("installs agent-native Truthmark workflow guidance without CLI-led runtime commands", async () => {
|
||||
@@ -29,6 +28,9 @@ describe("installed workflow contract", () => {
|
||||
const documentSkill = await repo.readFile(
|
||||
".agents/skills/truthmark-document/SKILL.md",
|
||||
);
|
||||
const syncCodexAdapter = await repo.readFile(
|
||||
".agents/skills/truthmark-sync/SKILL.md",
|
||||
);
|
||||
const syncOpenCodeSkill = await repo.readFile(
|
||||
".opencode/skills/truthmark-sync/SKILL.md",
|
||||
);
|
||||
@@ -61,7 +63,8 @@ describe("installed workflow contract", () => {
|
||||
expect(agents).not.toContain("### Truth Structure");
|
||||
expect(agents).toContain("### Truth Sync");
|
||||
expect(agents).not.toContain("### Truth Check");
|
||||
expect(agents).toContain(`Generated by Truthmark ${TRUTHMARK_VERSION}`);
|
||||
expect(agents).toContain("Truthmark-managed block");
|
||||
expect(agents).not.toContain("Generated by Truthmark");
|
||||
expect(agents).toContain("use the truthmark-sync skill before finishing");
|
||||
expect(agents).not.toContain("/skill truthmark-sync");
|
||||
expect(agents).not.toContain("Explicit invocation:");
|
||||
@@ -89,13 +92,21 @@ describe("installed workflow contract", () => {
|
||||
expect(syncSkill).toContain(
|
||||
"Use this skill automatically before finishing",
|
||||
);
|
||||
expect(syncSkill).toContain(`truthmark-version: ${TRUTHMARK_VERSION}`);
|
||||
expect(syncSkill).not.toContain(
|
||||
"truthmark check --json --workflow truth-sync",
|
||||
);
|
||||
expect(syncSkill).toContain(
|
||||
"direct checkout inspection is the canonical path",
|
||||
);
|
||||
expect(syncCodexAdapter).toContain(
|
||||
"Use this skill automatically before finishing",
|
||||
);
|
||||
expect(syncCodexAdapter).toContain("support/procedure.md");
|
||||
expect(
|
||||
await repo.readFile(
|
||||
".agents/skills/truthmark-sync/support/procedure.md",
|
||||
),
|
||||
).toContain("Parent workflow:");
|
||||
expect(documentSkill).toContain("name: truthmark-document");
|
||||
expect(documentSkill).toContain("support/procedure.md");
|
||||
expect(documentSkill).toContain("support/report-template.md");
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("Truthmark product boundary", () => {
|
||||
it("keeps spec/proposal lifecycle and workflow-engine drift out of scope", () => {
|
||||
const productBoundary = readFileSync(
|
||||
join(process.cwd(), "docs/architecture/product-boundary.md"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
expect(productBoundary).toContain(
|
||||
"a requirements-management, PRD, proposal, or spec lifecycle platform",
|
||||
);
|
||||
expect(productBoundary).toContain("an arbitrary workflow DAG engine");
|
||||
expect(productBoundary).toContain(
|
||||
"missing packages, CLIs, daemons, services, or plugins must not block normal workflow execution",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -49,7 +49,7 @@ describe("buildRepoIndex", () => {
|
||||
);
|
||||
expect(result.files.map((file) => file.path)).toContain("src/math.ts");
|
||||
expect(result.docs.map((doc) => doc.path)).toContain(
|
||||
"docs/truthmark/engineering/repository/overview.md",
|
||||
"docs/truthmark/engineering/repository/bootstrap-routing.md",
|
||||
);
|
||||
expect(result.files).toContainEqual(
|
||||
expect.objectContaining({ path: "AGENTS.md", kind: "generated" }),
|
||||
@@ -69,9 +69,15 @@ describe("buildRepoIndex", () => {
|
||||
it("keeps polyglot source files visible as workflow files without semantic distinctions", async () => {
|
||||
const repo = await createTempRepo();
|
||||
repos.push(repo);
|
||||
await repo.writeFile("cmd/server/main.go", "package main\n\nfunc main() {}\n");
|
||||
await repo.writeFile(
|
||||
"cmd/server/main.go",
|
||||
"package main\n\nfunc main() {}\n",
|
||||
);
|
||||
await repo.writeFile("scripts/task.py", "print('task')\n");
|
||||
await repo.writeFile("src/App/Program.cs", "namespace App;\n\npublic class Program {}\n");
|
||||
await repo.writeFile(
|
||||
"src/App/Program.cs",
|
||||
"namespace App;\n\npublic class Program {}\n",
|
||||
);
|
||||
await repo.writeFile(
|
||||
"src/main/java/com/example/App.java",
|
||||
"package com.example;\n\npublic class App {}\n",
|
||||
@@ -83,12 +89,36 @@ describe("buildRepoIndex", () => {
|
||||
|
||||
expect(result.files).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ path: "cmd/server/main.go", kind: "source", language: null }),
|
||||
expect.objectContaining({ path: "scripts/task.py", kind: "source", language: null }),
|
||||
expect.objectContaining({ path: "src/App/Program.cs", kind: "source", language: null }),
|
||||
expect.objectContaining({ path: "src/main/java/com/example/App.java", kind: "source", language: null }),
|
||||
expect.objectContaining({ path: "src/index.ts", kind: "source", language: "typescript" }),
|
||||
expect.objectContaining({ path: "src/index.js", kind: "source", language: "javascript" }),
|
||||
expect.objectContaining({
|
||||
path: "cmd/server/main.go",
|
||||
kind: "source",
|
||||
language: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: "scripts/task.py",
|
||||
kind: "source",
|
||||
language: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: "src/App/Program.cs",
|
||||
kind: "source",
|
||||
language: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: "src/main/java/com/example/App.java",
|
||||
kind: "source",
|
||||
language: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: "src/index.ts",
|
||||
kind: "source",
|
||||
language: "typescript",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: "src/index.js",
|
||||
kind: "source",
|
||||
language: "javascript",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(result).not.toHaveProperty("imports");
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
@@ -37,6 +40,9 @@ Ownership reviewed:
|
||||
Truth docs updated:
|
||||
- docs/truthmark/engineering/behaviors/authentication.md
|
||||
|
||||
Decision/rationale captured:
|
||||
- none provided in task conversation
|
||||
|
||||
Evidence checked:
|
||||
- Claim: Session timeout behavior is documented in the authentication truth doc.
|
||||
Evidence: src/auth/session.ts:12 / docs/truthmark/routes/areas/repository.md:18
|
||||
@@ -49,6 +55,7 @@ Notes:
|
||||
changedCode: ["src/auth/session.ts"],
|
||||
ownershipReviewed: ["docs/truthmark/routes/areas/repository.md"],
|
||||
truthDocsUpdated: ["docs/truthmark/engineering/behaviors/authentication.md"],
|
||||
decisionRationaleCaptured: ["none provided in task conversation"],
|
||||
evidenceChecked: [
|
||||
{
|
||||
claim: "Session timeout behavior is documented in the authentication truth doc.",
|
||||
@@ -63,6 +70,87 @@ Notes:
|
||||
});
|
||||
});
|
||||
|
||||
it("round-trips optional Sync Intent with user decision context capture", () => {
|
||||
const syncIntent = {
|
||||
changedCodeReviewed: ["src/auth/session.ts"],
|
||||
affectedRouteOrTruthOwner: ["docs/truthmark/routes/areas/repository.md"],
|
||||
targetTruthDocs: ["docs/truthmark/engineering/behaviors/authentication.md"],
|
||||
intendedUpdate: ["Update documented session timeout behavior."],
|
||||
evidenceToVerify: ["src/auth/session.ts:12"],
|
||||
userProvidedDecisionRationale: [
|
||||
"User decision: preserve a 30 minute timeout because long-lived sessions are out of scope",
|
||||
"Lane: engineering contract",
|
||||
],
|
||||
noUpdateNeededRationale: ["not applicable; mapped truth is stale"],
|
||||
blockers: ["none"],
|
||||
};
|
||||
const report = renderTruthSyncCompletedReport({
|
||||
changedCode: ["src/auth/session.ts"],
|
||||
syncIntent,
|
||||
ownershipReviewed: ["docs/truthmark/routes/areas/repository.md"],
|
||||
truthDocsUpdated: ["docs/truthmark/engineering/behaviors/authentication.md"],
|
||||
evidenceChecked: [
|
||||
{
|
||||
claim: "Session timeout behavior is documented in the authentication truth doc.",
|
||||
evidence: ["src/auth/session.ts:12"],
|
||||
result: "supported",
|
||||
},
|
||||
],
|
||||
decisionRationaleCaptured: [
|
||||
"Placed user rationale in docs/truthmark/engineering/behaviors/authentication.md under Engineering Decisions and Rationale.",
|
||||
],
|
||||
notes: ["Updated session timeout behavior."],
|
||||
});
|
||||
|
||||
expect(report).toContain(`Sync Intent:
|
||||
- Changed code reviewed: src/auth/session.ts
|
||||
- Affected route/truth owner: docs/truthmark/routes/areas/repository.md
|
||||
- Target truth docs: docs/truthmark/engineering/behaviors/authentication.md
|
||||
- Intended update: Update documented session timeout behavior.
|
||||
- Evidence to verify: src/auth/session.ts:12
|
||||
- User-provided decisions/rationale: User decision: preserve a 30 minute timeout because long-lived sessions are out of scope / Lane: engineering contract
|
||||
- No-update-needed rationale: not applicable; mapped truth is stale
|
||||
- Blockers: none`);
|
||||
expect(report).toContain(`Decision/rationale captured:
|
||||
- Placed user rationale in docs/truthmark/engineering/behaviors/authentication.md under Engineering Decisions and Rationale.`);
|
||||
expect(parseTruthSyncReport(report)).toMatchObject({
|
||||
syncIntent,
|
||||
decisionRationaleCaptured: [
|
||||
"Placed user rationale in docs/truthmark/engineering/behaviors/authentication.md under Engineering Decisions and Rationale.",
|
||||
],
|
||||
changedCode: ["src/auth/session.ts"],
|
||||
truthDocsUpdated: ["docs/truthmark/engineering/behaviors/authentication.md"],
|
||||
});
|
||||
});
|
||||
|
||||
it("requires completed reports to state how user decision rationale was captured", () => {
|
||||
expect(() =>
|
||||
parseTruthSyncReport(`Truth Sync: completed
|
||||
|
||||
Changed code reviewed:
|
||||
- src/auth/session.ts
|
||||
|
||||
Ownership reviewed:
|
||||
- docs/truthmark/routes/areas/repository.md
|
||||
|
||||
Truth docs updated:
|
||||
- docs/truthmark/engineering/behaviors/authentication.md
|
||||
|
||||
Evidence checked:
|
||||
- Claim: Session timeout behavior is documented.
|
||||
Evidence: src/auth/session.ts:12
|
||||
Result: supported
|
||||
|
||||
Notes:
|
||||
- Updated session timeout behavior.`),
|
||||
).toThrow("Decision/rationale captured section is required");
|
||||
});
|
||||
|
||||
it("does not introduce persistent Sync Plan or lifecycle artifacts", () => {
|
||||
expect(existsSync(join(process.cwd(), "src/sync/plan.ts"))).toBe(false);
|
||||
expect(existsSync(join(process.cwd(), "truthmark/changes"))).toBe(false);
|
||||
});
|
||||
|
||||
it("round-trips optional helper script statuses", () => {
|
||||
const report = renderTruthSyncCompletedReport({
|
||||
changedCode: ["src/auth/session.ts"],
|
||||
@@ -128,6 +216,9 @@ Changed code reviewed:
|
||||
Truth docs updated:
|
||||
- docs/truthmark/engineering/behaviors/authentication.md
|
||||
|
||||
Decision/rationale captured:
|
||||
- none provided in task conversation
|
||||
|
||||
Evidence checked:
|
||||
- Session timeout behavior was reviewed.
|
||||
|
||||
@@ -146,6 +237,9 @@ Changed code reviewed:
|
||||
Truth docs updated:
|
||||
- docs/truthmark/engineering/behaviors/authentication.md
|
||||
|
||||
Decision/rationale captured:
|
||||
- none provided in task conversation
|
||||
|
||||
Evidence checked:
|
||||
- Claim:${" "}
|
||||
Evidence: src/auth/session.ts:12
|
||||
@@ -164,6 +258,9 @@ Changed code reviewed:
|
||||
Truth docs updated:
|
||||
- docs/truthmark/engineering/behaviors/authentication.md
|
||||
|
||||
Decision/rationale captured:
|
||||
- none provided in task conversation
|
||||
|
||||
Evidence checked:
|
||||
- Claim: Session timeout behavior is documented.
|
||||
Evidence:${" "}
|
||||
|
||||
@@ -21,14 +21,22 @@ const portalPaths = [
|
||||
const readOnlyProcedurePaths = [
|
||||
".agents/skills/truthmark-check/support/procedure.md",
|
||||
".agents/skills/truthmark-preview/support/procedure.md",
|
||||
".opencode/skills/truthmark-check/support/procedure.md",
|
||||
".opencode/skills/truthmark-preview/support/procedure.md",
|
||||
".claude/skills/truthmark-check/support/procedure.md",
|
||||
".claude/skills/truthmark-preview/support/procedure.md",
|
||||
".gemini/skills/truthmark-check/support/procedure.md",
|
||||
".gemini/skills/truthmark-preview/support/procedure.md",
|
||||
".github/skills/truthmark-check/support/procedure.md",
|
||||
".github/skills/truthmark-preview/support/procedure.md",
|
||||
".opencode/skills/truthmark-check/support/procedure.md",
|
||||
".opencode/skills/truthmark-preview/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",
|
||||
".gemini/skills/truthmark-sync/support/procedure.md",
|
||||
".github/skills/truthmark-sync/support/procedure.md",
|
||||
];
|
||||
|
||||
const staleWriteAuthorizingLaneText =
|
||||
@@ -48,6 +56,32 @@ describe("Truthmark Portal generated surfaces", () => {
|
||||
}
|
||||
});
|
||||
|
||||
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, procedurePath).toContain("Product truth decision");
|
||||
expect(content, procedurePath).toContain(
|
||||
"ask whether a user-visible promise, capability boundary, API contract, acceptance criterion, or explicit user/product evidence changed",
|
||||
);
|
||||
expect(content, procedurePath).toContain(
|
||||
"if no, default to engineering truth under docs/truthmark/engineering for internal implementation changes",
|
||||
);
|
||||
expect(content, procedurePath).toContain(
|
||||
"Product truth is opt-in for externally visible promises, product boundaries, APIs, acceptance criteria, or explicit user/product evidence.",
|
||||
);
|
||||
expect(content, procedurePath).toContain(
|
||||
"User-provided decisions/rationale",
|
||||
);
|
||||
expect(content, procedurePath).not.toContain(
|
||||
staleWriteAuthorizingLaneText,
|
||||
);
|
||||
expect(content, procedurePath).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();
|
||||
const generatedSurfaces = renderGeneratedSurfaces(config);
|
||||
@@ -64,6 +98,11 @@ describe("Truthmark Portal generated surfaces", () => {
|
||||
"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);
|
||||
@@ -90,17 +129,16 @@ describe("Truthmark Portal generated surfaces", () => {
|
||||
const syncHelperManifest =
|
||||
byPath.get(".agents/skills/truthmark-sync/helper-manifest.yml") ?? "";
|
||||
const syncHelperPolicy =
|
||||
byPath.get(".agents/skills/truthmark-sync/support/helper-policy.md") ??
|
||||
"";
|
||||
byPath.get(".agents/skills/truthmark-sync/support/helper-policy.md") ?? "";
|
||||
const previewSkill =
|
||||
byPath.get(".agents/skills/truthmark-preview/SKILL.md") ?? "";
|
||||
|
||||
expect(syncSkill).toContain("Quick procedure:");
|
||||
expect(syncSkill).toContain(
|
||||
"direct checkout inspection is the canonical path",
|
||||
);
|
||||
expect(syncSkill).toContain(
|
||||
"Read support/procedure.md before editing truth docs.",
|
||||
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(
|
||||
@@ -116,6 +154,39 @@ describe("Truthmark Portal generated surfaces", () => {
|
||||
expect(previewSkill).not.toContain("CLI is unavailable");
|
||||
});
|
||||
|
||||
it("does not render unused repo-local agent package copies", () => {
|
||||
const config = createDefaultConfig();
|
||||
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();
|
||||
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("omits Portal surfaces and AGENTS wording when disabled", () => {
|
||||
const config = createDefaultConfig();
|
||||
const paths = renderGeneratedSurfaces(config).map(
|
||||
|
||||
@@ -84,24 +84,34 @@ describe("workflow state contract", () => {
|
||||
schemaVersion: "truthmark-workflow/v0",
|
||||
workflow: "truthmark-check",
|
||||
applicability: {
|
||||
state: "applicable",
|
||||
state: "ready",
|
||||
reasons: [],
|
||||
},
|
||||
actionContext: {
|
||||
mode: "read-only",
|
||||
allowedWritePaths: [],
|
||||
routeFiles: [],
|
||||
primaryTruthDocs: [],
|
||||
candidateStaleTruthDocs: [],
|
||||
forbiddenWritePaths: [],
|
||||
stopConditions: [],
|
||||
requiredEvidence: [],
|
||||
evidencePrompts: [],
|
||||
helperValidationCommands: [],
|
||||
writeLeaseRequired: false,
|
||||
},
|
||||
workflowCard: {
|
||||
affectedFiles: [],
|
||||
likelyRouteOwners: [],
|
||||
suggestedTruthDocs: [],
|
||||
openQuestions: [],
|
||||
skippedHelperStatus: [],
|
||||
},
|
||||
changedFiles: [],
|
||||
affectedRoutes: [],
|
||||
targetTruthDocs: [],
|
||||
diagnostics: [],
|
||||
checks: {
|
||||
required: [],
|
||||
reviewChecklist: [],
|
||||
recommended: [],
|
||||
helpers: [],
|
||||
affectedTests: [],
|
||||
@@ -240,9 +250,21 @@ describe("buildWorkflowState", () => {
|
||||
expect(state.actionContext.allowedWritePaths).toEqual(
|
||||
expect.arrayContaining(state.targetTruthDocs),
|
||||
);
|
||||
expect(state.checks.required).toEqual(
|
||||
expect.arrayContaining(TRUTHMARK_WORKFLOW_MANIFEST["truthmark-sync"].requiredGates),
|
||||
expect(state.checks.reviewChecklist).toEqual(
|
||||
expect.arrayContaining(TRUTHMARK_WORKFLOW_MANIFEST["truthmark-sync"].reviewQuestions),
|
||||
);
|
||||
expect(state.actionContext.evidencePrompts.join("\n")).toContain("Evidence checklist");
|
||||
expect(state.workflowCard.affectedFiles).toContain("src/math.ts");
|
||||
expect(state.workflowCard.likelyRouteOwners.length).toBeGreaterThan(0);
|
||||
expect(state.workflowCard.suggestedTruthDocs).toEqual(state.targetTruthDocs);
|
||||
expect(state.workflowCard.skippedHelperStatus).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ helper: "validate-sync-report", status: "skipped" }),
|
||||
]),
|
||||
);
|
||||
expect(JSON.stringify(state)).not.toContain(["required", "Gates"].join(""));
|
||||
expect(JSON.stringify(state)).not.toContain("requiredEvidence");
|
||||
expect(JSON.stringify(state)).not.toContain("reviewQuestions");
|
||||
expect(state.checks.helpers.map((helper) => helper.id)).toContain("validate-sync-report");
|
||||
expect(JSON.stringify((state.checks as { affectedTests?: string[] }).affectedTests ?? [])).toContain(
|
||||
"tests/math.test.ts",
|
||||
@@ -268,10 +290,10 @@ describe("buildWorkflowState", () => {
|
||||
expect(state.affectedRoutes.map((route) => route.sourcePath)).toEqual([
|
||||
"docs/truthmark/routes/areas/repository.md",
|
||||
]);
|
||||
expect(state.targetTruthDocs).toEqual(["docs/truthmark/engineering/repository/overview.md"]);
|
||||
expect(state.targetTruthDocs).toEqual(["docs/truthmark/engineering/repository/bootstrap-routing.md"]);
|
||||
expect(state.actionContext.allowedWritePaths).toEqual(
|
||||
expect.arrayContaining([
|
||||
"docs/truthmark/engineering/repository/overview.md",
|
||||
"docs/truthmark/engineering/repository/bootstrap-routing.md",
|
||||
"docs/truthmark/engineering/unrelated.md",
|
||||
"docs/truthmark/routes/areas.md",
|
||||
"docs/truthmark/routes/areas/repository.md",
|
||||
@@ -316,8 +338,8 @@ describe("buildWorkflowState", () => {
|
||||
|
||||
const state = await buildWorkflowState(repo.rootDir, { workflow: "truthmark-sync" });
|
||||
|
||||
expect(state.applicability.state).toBe("blocked");
|
||||
expect(state.applicability.reasons.join("\n")).toContain("truthmark-sync requires --base");
|
||||
expect(state.applicability.state).toBe("needs_manual_review");
|
||||
expect(state.applicability.reasons.join("\n")).toContain("Choose a comparison base with --base <ref>");
|
||||
expect(state.actionContext.allowedWritePaths).toEqual([]);
|
||||
expect(state.changedFiles).toEqual([]);
|
||||
expect(state.targetTruthDocs).toEqual([]);
|
||||
@@ -331,7 +353,7 @@ describe("buildWorkflowState", () => {
|
||||
|
||||
const state = await buildWorkflowState(repo.rootDir, { workflow: "truthmark-sync" });
|
||||
|
||||
expect(["blocked", "not_applicable"]).toContain(state.applicability.state);
|
||||
expect(["needs_manual_review", "not_applicable"]).toContain(state.applicability.state);
|
||||
expect(state.applicability.reasons.join("\n")).toContain("Missing .truthmark/config.yml");
|
||||
expect(state.actionContext.allowedWritePaths).toEqual([]);
|
||||
});
|
||||
@@ -356,7 +378,7 @@ describe("buildWorkflowState", () => {
|
||||
base: "main",
|
||||
});
|
||||
|
||||
expect(["ambiguous", "blocked"]).toContain(state.applicability.state);
|
||||
expect(["needs_routing_review", "needs_manual_review"]).toContain(state.applicability.state);
|
||||
expect(state.targetTruthDocs).toEqual([]);
|
||||
expect(state.nextSteps.join("\n")).toMatch(/Truth Structure|route repair/u);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user