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:
Merlin's Cat
2026-06-18 17:07:40 +10:00
committed by GitHub
co-authored by MerlinH
parent e9cd67f0b5
commit fb2c469005
233 changed files with 3657 additions and 3655 deletions
+2 -2
View File
@@ -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");
+2 -2
View File
@@ -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");
+1 -3
View File
@@ -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"',
);
});
+9 -11
View File
@@ -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.",
+1 -3
View File
@@ -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");
+10 -12
View File
@@ -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"',
);
});
});
+55 -32
View File
@@ -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.
`,
-201
View File
@@ -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
View File
@@ -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"),
}),
+34 -4
View File
@@ -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");
+38
View File
@@ -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 {
-525
View File
@@ -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
View File
@@ -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");
});
});
+6 -3
View File
@@ -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");
+21
View File
@@ -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",
);
});
});
+39 -9
View File
@@ -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");
+97
View File
@@ -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:${" "}
+81 -10
View File
@@ -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(
+33 -11
View File
@@ -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);
});