truthmark 2.2.1 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -256,13 +256,7 @@ var SUPPORTED_PLATFORMS = [
256
256
  "github-copilot",
257
257
  "gemini-cli"
258
258
  ];
259
- var DEFAULT_PLATFORMS = [
260
- "codex",
261
- "opencode",
262
- "claude-code",
263
- "github-copilot",
264
- "gemini-cli"
265
- ];
259
+ var DEFAULT_PLATFORMS = [];
266
260
  var truthmarkConfigSchema = {
267
261
  type: "object",
268
262
  additionalProperties: false,
@@ -368,7 +362,6 @@ var DERIVED_TRUTHMARK_PATHS = {
368
362
  var DEFAULT_INSTRUCTION_TARGETS = ["AGENTS.md"];
369
363
  var createDefaultRawConfig = () => ({
370
364
  version: 2,
371
- platforms: [...DEFAULT_PLATFORMS],
372
365
  truthmark: {
373
366
  workspace: DEFAULT_TRUTHMARK_WORKSPACE.workspace,
374
367
  generated: {
@@ -853,7 +846,7 @@ var renderChildAreaTemplate = (config) => {
853
846
  const defaultArea = config.truthmark.routes.defaultArea;
854
847
  const title = titleCase(defaultArea);
855
848
  const truthDocsRoot = truthRoot(config);
856
- const leafTruthDoc = `${truthDocsRoot}/${defaultArea}/overview.md`;
849
+ const bootstrapTruthDoc = `${truthDocsRoot}/${defaultArea}/bootstrap-routing.md`;
857
850
  const templatePath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
858
851
  const sourceOfTruth = resolveRelativePath(
859
852
  templatePath,
@@ -873,16 +866,20 @@ var renderChildAreaTemplate = (config) => {
873
866
  "Truth documents:",
874
867
  "```yaml",
875
868
  "truth_documents:",
876
- ` - path: ${leafTruthDoc}`,
877
- " kind: engineering-behavior",
869
+ ` - path: ${bootstrapTruthDoc}`,
870
+ " kind: engineering-workflow",
878
871
  " lane: engineering",
879
872
  "```",
880
873
  "",
874
+ "This is a provisional bootstrap route. It exists only to make fresh repositories routeable until real product, service, domain, or ownership areas are created.",
875
+ "",
881
876
  "Code surface:",
882
877
  "- src/**",
883
878
  "",
884
879
  "Update truth when:",
885
- "- behavior changes affect repository truth",
880
+ "- this provisional bootstrap route is the only match for a real code surface",
881
+ "- route ownership is still broad, mixed, or ambiguous",
882
+ "- Run Truth Structure before normal Truth Sync so the touched code gets a bounded owner",
886
883
  "",
887
884
  "## Source References",
888
885
  "",
@@ -890,6 +887,103 @@ var renderChildAreaTemplate = (config) => {
890
887
  ""
891
888
  ].join("\n");
892
889
  };
890
+ var renderBootstrapRoutingDocTemplate = (config) => {
891
+ const defaultArea = config.truthmark.routes.defaultArea;
892
+ const title = titleCase(defaultArea);
893
+ const templatePath = `${truthRoot(config)}/${defaultArea}/bootstrap-routing.md`;
894
+ const routePath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
895
+ const routeSource = resolveRelativePath(templatePath, routePath);
896
+ const configSource = resolveRelativePath(
897
+ templatePath,
898
+ ".truthmark/config.yml"
899
+ );
900
+ const today = currentDate();
901
+ return [
902
+ "---",
903
+ "status: active",
904
+ "truth_kind: engineering-workflow",
905
+ `last_reviewed: ${today}`,
906
+ "---",
907
+ "",
908
+ `# ${title} Bootstrap Routing`,
909
+ "",
910
+ "## Purpose",
911
+ "",
912
+ `This doc records the provisional broad route for ${defaultArea}.`,
913
+ "This doc is a bootstrap handoff, not a behavior truth dumping ground.",
914
+ "It is not a substitute for bounded product and engineering truth docs.",
915
+ "",
916
+ "## Scope",
917
+ "",
918
+ "This doc owns only the initial routing workflow for a fresh Truthmark repository whose default route still maps a broad code surface such as `src/**`.",
919
+ "It does not own implementation behavior under that code surface.",
920
+ "",
921
+ "## Current Implementation Behavior",
922
+ "",
923
+ "The scaffold creates this provisional bootstrap handoff only when a default broad route needs a canonical owner. Agents use it as a signal to run Truth Structure and create bounded routes before normal Truth Sync, not as a place to accumulate implementation claims.",
924
+ "",
925
+ "## Product Truth Links",
926
+ "",
927
+ "- None. This is an engineering bootstrap handoff for routing setup, not a product promise.",
928
+ "",
929
+ "## Triggers",
930
+ "",
931
+ "- A real code change maps only to this provisional broad route.",
932
+ "- Truth Sync cannot identify a specific behavior-owned route and bounded truth owner.",
933
+ "- A maintainer or agent is onboarding the first real product, service, domain, package, or ownership area.",
934
+ "",
935
+ "## Inputs",
936
+ "",
937
+ "- Current route files under the configured Truthmark route root.",
938
+ "- The touched code, tests, configuration, and existing docs needed to infer the smallest real owner.",
939
+ "- Repository instruction files that exist in the checkout.",
940
+ "",
941
+ "## Execution Model",
942
+ "",
943
+ "Run Truth Structure before normal Truth Sync when real code changes touch only this broad route. Truth Structure should create or repair bounded areas first; Truth Sync should then update the bounded owner docs.",
944
+ "",
945
+ "## Steps",
946
+ "",
947
+ "1. Treat this route as provisional and insufficient for normal behavior maintenance.",
948
+ "2. Inspect the touched code/test surface and infer the narrowest durable owner.",
949
+ "3. Create or repair route entries and truth docs for that owner.",
950
+ "4. Leave this bootstrap doc small; do not append behavior details here.",
951
+ "5. Resume Truth Sync only after the touched code resolves to a bounded owner.",
952
+ "",
953
+ "## State, Retry, And Failure Behavior",
954
+ "",
955
+ "If ownership cannot be inferred safely, stop and report manual-review files instead of widening this route or adding generic behavior prose.",
956
+ "",
957
+ "## Outputs",
958
+ "",
959
+ "- Bounded route areas and lane-appropriate truth docs for the touched surface.",
960
+ "- A compact manual handoff report when ownership remains ambiguous.",
961
+ "",
962
+ "## Engineering Decisions",
963
+ "",
964
+ `- Decision (${today}): Default broad routing is provisional bootstrap state. Agents should create bounded areas before normal Truth Sync rather than extending a catch-all overview doc.`,
965
+ "",
966
+ "## Rationale",
967
+ "",
968
+ "Scoped ownership keeps agent context close to affected files and prevents broad default docs from absorbing unrelated behavior. This preserves agent-native truth maintenance without adding a token-heavy discovery layer.",
969
+ "",
970
+ "## Non-Goals",
971
+ "",
972
+ "- This doc is not a repository behavior overview.",
973
+ "- This doc is not a product capability or engineering behavior owner.",
974
+ "- This doc is not a permanent home for claims about files under `src/**`.",
975
+ "",
976
+ "## Maintenance Notes",
977
+ "",
978
+ "Keep this doc short. When a repository has real bounded routes, prefer updating those routes and their truth docs instead of expanding this bootstrap handoff.",
979
+ "",
980
+ "## Source References",
981
+ "",
982
+ `- ${routeSource}`,
983
+ `- ${configSource}`,
984
+ ""
985
+ ].join("\n");
986
+ };
893
987
  var renderTruthRootReadmeTemplate = (config = createDefaultConfig(), lane = "engineering") => {
894
988
  const templatePath = `${lane === "product" ? resolveProductTruthRoot(config) : resolveEngineeringTruthRoot(config)}/README.md`;
895
989
  const sourceOfTruth = resolveRelativePath(
@@ -940,7 +1034,7 @@ var renderTruthDomainReadmeTemplate = (config) => {
940
1034
  "",
941
1035
  "Current leaf docs:",
942
1036
  "",
943
- "- [Overview](overview.md)",
1037
+ "- [Bootstrap routing](bootstrap-routing.md)",
944
1038
  "",
945
1039
  "## Source References",
946
1040
  "",
@@ -948,7 +1042,6 @@ var renderTruthDomainReadmeTemplate = (config) => {
948
1042
  ""
949
1043
  ].join("\n");
950
1044
  };
951
- var BEHAVIOR_DOC_TEMPLATE_PATH = "docs/truthmark/templates/engineering-behavior.md";
952
1045
  var renderTemplateSection = (section) => {
953
1046
  return [
954
1047
  section.heading,
@@ -1363,7 +1456,7 @@ var renderContractDocTemplateFile = () => {
1363
1456
  ]),
1364
1457
  sectionSpec("## Compatibility Rules", [
1365
1458
  "State backward/forward compatibility guarantees, tolerated inputs, deprecation rules, and breaking-change triggers.",
1366
- "Include compatibility tests or review gates that protect the contract."
1459
+ "Include compatibility tests or review questions that protect the contract."
1367
1460
  ]),
1368
1461
  sectionSpec("## Versioning And Migration", [
1369
1462
  "Document version negotiation, schema/API version fields, rollout requirements, migration steps, and rollback expectations.",
@@ -1411,7 +1504,7 @@ var renderWorkflowDocTemplateFile = () => {
1411
1504
  ]),
1412
1505
  sectionSpec("## Execution Model", [
1413
1506
  "Describe synchronous/asynchronous execution, concurrency, locking, leases, batching, ordering, and idempotency behavior.",
1414
- "State whether the workflow is user-blocking, background, distributed, or delegated to another system."
1507
+ "State whether the workflow waits for user action, runs in the background, is distributed, or is delegated to another system."
1415
1508
  ]),
1416
1509
  sectionSpec("## Steps", [
1417
1510
  "Capture the current ordered steps or phases at a level useful for maintenance and review.",
@@ -1447,7 +1540,7 @@ var renderOperationsDocTemplateFile = () => {
1447
1540
  ]),
1448
1541
  sectionSpec("## Deployment And Rollback", [
1449
1542
  "Describe deployment mechanism, migration ordering, compatibility windows, rollback path, and known irreversible operations.",
1450
- "Call out manual gates, smoke checks, and post-deploy verification responsibilities."
1543
+ "Call out manual review points, smoke checks, and post-deploy verification responsibilities."
1451
1544
  ]),
1452
1545
  sectionSpec("## Availability And Observability", [
1453
1546
  "Capture availability expectations, health checks, metrics, logs, traces, alerts, SLO/error-budget signals, and known blind spots.",
@@ -1483,39 +1576,6 @@ var renderTestBehaviorDocTemplateFile = () => {
1483
1576
  ])
1484
1577
  ]);
1485
1578
  };
1486
- var renderTemplate = (template, values) => {
1487
- return Object.entries(values).reduce((rendered, [key, value]) => {
1488
- return rendered.split(`{{${key}}}`).join(value);
1489
- }, template);
1490
- };
1491
- var renderBehaviorLeafDocTemplate = (config, template = renderBehaviorDocTemplateFile()) => {
1492
- const defaultArea = config.truthmark.routes.defaultArea;
1493
- const title = titleCase(defaultArea);
1494
- const templatePath = `${truthRoot(config)}/${defaultArea}/overview.md`;
1495
- const sourceOfTruth = resolveRelativePath(
1496
- templatePath,
1497
- `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`
1498
- );
1499
- const today = currentDate();
1500
- return renderTemplate(template, {
1501
- area: defaultArea,
1502
- contracts: "- External contracts should link to the nearest canonical contract doc when one exists.",
1503
- core_rules: "- Truth README files are indexes; behavior truth belongs in bounded leaf docs.",
1504
- current_implementation_behavior: "- Document current behavior here when implementation changes make repository truth incomplete.",
1505
- engineering_decisions: `- Decision (${today}): Truth README files are indexes; behavior truth belongs in bounded leaf docs.`,
1506
- flows_and_states: "- None beyond current behavior.",
1507
- maintenance_notes: "- Update this doc when routed implementation changes alter current behavior, rules, contracts, or decisions.",
1508
- non_goals: "- This doc is not a catch-all for unrelated repository behavior.",
1509
- purpose: `Describe why the default ${title.toLowerCase()} behavior surface exists and what outcome it protects.`,
1510
- rationale: "Bounded leaf docs keep agent context focused and prevent large products from accumulating unreviewable feature manuals.",
1511
- product_truth_links: "- None.",
1512
- scope: `This bounded leaf truth doc owns the default ${title.toLowerCase()} behavior surface created by Truthmark.`,
1513
- source_references: `- ${sourceOfTruth}`,
1514
- template_path: BEHAVIOR_DOC_TEMPLATE_PATH,
1515
- title: `${title} Overview`,
1516
- truth_kind: "engineering-behavior"
1517
- });
1518
- };
1519
1579
 
1520
1580
  // src/config/command.ts
1521
1581
  var CONFIG_PATH = ".truthmark/config.yml";
@@ -1840,22 +1900,6 @@ var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePat
1840
1900
  var truthTemplatePath = (config, fileName) => {
1841
1901
  return `${config.truthmark.paths.templatesRoot}/${fileName}`;
1842
1902
  };
1843
- var readBehaviorDocTemplate = async (rootDir, config) => {
1844
- try {
1845
- return await fs5.readFile(
1846
- resolveRepoPath(
1847
- rootDir,
1848
- truthTemplatePath(config, BEHAVIOR_DOC_TEMPLATE_FILE_NAME)
1849
- ),
1850
- "utf8"
1851
- );
1852
- } catch (error) {
1853
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1854
- return renderBehaviorDocTemplateFile();
1855
- }
1856
- throw error;
1857
- }
1858
- };
1859
1903
  var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTemplate) => {
1860
1904
  const seededResult = await ensureRepoFile(
1861
1905
  rootDir,
@@ -1970,17 +2014,19 @@ var scaffoldHierarchy = async (rootDir, config) => {
1970
2014
  renderProductCapabilityDocTemplateFile()
1971
2015
  )
1972
2016
  );
1973
- const behaviorDocTemplate = await readBehaviorDocTemplate(rootDir, config);
1974
2017
  results.push(
1975
2018
  await ensureRepoFile(
1976
2019
  rootDir,
1977
- `${truthDomainRoot}/overview.md`,
1978
- renderBehaviorLeafDocTemplate(config, behaviorDocTemplate)
2020
+ `${truthDomainRoot}/bootstrap-routing.md`,
2021
+ renderBootstrapRoutingDocTemplate(config)
1979
2022
  )
1980
2023
  );
1981
2024
  return results;
1982
2025
  };
1983
2026
 
2027
+ // src/checks/generated-surfaces.ts
2028
+ import fs6 from "fs/promises";
2029
+
1984
2030
  // src/truth/evidence.ts
1985
2031
  var renderClaimEvidenceCheckedSection = (items) => {
1986
2032
  return [
@@ -2029,7 +2075,7 @@ var renderReadOnlyLaneClassificationRuleBlock = (config = defaultAgentConfig(),
2029
2075
  `product-lane ownership belongs under ${productTruthRoot} and describes product promises, boundaries, rationale, decisions, and success criteria`,
2030
2076
  `engineering-lane ownership belongs under ${engineeringTruthRoot} and describes source-backed current realization, contracts, architecture, workflows, operations, or tests`,
2031
2077
  "both-lane ownership uses separate product and engineering docs cross-linked in route YAML with realized_by and realizes, not in doc frontmatter",
2032
- "ambiguous lane ownership should be reported as blocked or routed to Truth Structure",
2078
+ "ambiguous lane ownership should be reported for manual handoff or routed to Truth Structure",
2033
2079
  LANE_INVARIANT
2034
2080
  ].join("\n"),
2035
2081
  indent
@@ -2045,12 +2091,12 @@ var renderLaneClassificationInstructions = (config = defaultAgentConfig()) => {
2045
2091
  const productTruthRoot = resolveProductTruthRoot(config);
2046
2092
  const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2047
2093
  return [
2048
- "Lane classification gate:",
2094
+ "Lane review questions:",
2049
2095
  "- before writing canonical truth docs, classify the request or change as product-lane, engineering-lane, both-lane, or ambiguous",
2050
2096
  `- product-lane writes belong under ${productTruthRoot} and state product promises, boundaries, rationale, decisions, and success criteria`,
2051
2097
  `- engineering-lane writes belong under ${engineeringTruthRoot} and state source-backed current realization, contracts, architecture, workflows, operations, or tests`,
2052
2098
  "- both-lane work must write separate product and engineering docs and cross-link them in route YAML with realized_by and realizes, not in doc frontmatter",
2053
- "- ambiguous lane ownership must block or invoke Truth Structure instead of writing a mixed document",
2099
+ "- ambiguous lane ownership must stop or invoke Truth Structure instead of writing a mixed document",
2054
2100
  `- ${LANE_INVARIANT}`
2055
2101
  ].join("\n");
2056
2102
  };
@@ -2071,31 +2117,32 @@ var FEATURE_DOC_TEMPLATE_INSTRUCTIONS = [
2071
2117
  "If the template is missing, use lane-specific sections: product truth says what must be true and why; engineering truth says how the repository currently realizes it.",
2072
2118
  "Teams may edit template files under the configured Truthmark templates root to define their local truth-doc standards."
2073
2119
  ].join("\n");
2120
+ var TRUTH_DOC_AUTHORING_STYLE_INSTRUCTIONS = "Prefer diff-friendly Markdown: one durable claim per bullet or line, paragraphs no longer than one or two short sentences, and bullets or tables for rules, criteria, fields, files, and lists.";
2074
2121
  var renderTruthDocOwnershipGateSection = (subject, outcome) => {
2075
2122
  return [
2076
- "Truth-doc ownership gate:",
2123
+ "Truth-doc ownership review:",
2077
2124
  `- before editing or relying on ${subject}, verify each target/source truth doc is a bounded owner for the behavior`,
2078
2125
  "- if a target/source doc mixes independent owners, spans unrelated behaviors, acts as an index, or needs cross-owner edits, do not patch or in-place repair it",
2079
2126
  `- ${outcome}`,
2080
- "- report Ownership reviewed, Structure required, Truth docs split, Truth docs restructured, or Blocked reason as applicable"
2127
+ "- report Ownership reviewed, Structure required, Truth docs split, Truth docs restructured, or Manual handoff reason as applicable"
2081
2128
  ].join("\n");
2082
2129
  };
2083
2130
  var TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS = [
2084
- "Decision/Rationale preservation gate:",
2131
+ "Decision/Rationale preservation review:",
2085
2132
  "- before any truth-doc split, restructure, or shape repair, inventory existing Product Decisions, Engineering Decisions, and Rationale sections in every source or touched truth doc",
2086
2133
  "- preserve each current decision and rationale in the correct product or engineering lane owner; when splitting, move it to the new owner doc rather than deleting it or leaving it in an index",
2087
2134
  "- remove or narrow a decision or rationale only when checkout evidence shows it is stale or unsupported, and report the exact claim, evidence, and result",
2088
- "- if ownership of a decision or rationale is unclear, block with manual-review files instead of deleting it or guessing",
2135
+ "- if ownership of a decision or rationale is unclear, stop with manual-review files instead of deleting it or guessing",
2089
2136
  "- after the edit, verify every touched truth doc keeps lane-appropriate decision/rationale sections and every pre-existing entry is preserved, moved, narrowed, removed with evidence, or blocked"
2090
2137
  ].join("\n");
2091
2138
  var renderTruthDocRestructureGateSection = (scope) => {
2092
2139
  return [
2093
- "Truth-doc shape repair gate:",
2140
+ "Truth-doc shape repair review:",
2094
2141
  `- ${scope}`,
2095
- "- repair shape in place only after the ownership gate confirms the doc is the right bounded owner",
2142
+ "- repair shape in place only after the ownership review confirms the doc is the right bounded owner",
2096
2143
  "- use Truth Structure for ownership splits; do not treat broad or mixed-owner docs as in-place repair work",
2097
2144
  "- repair shape when a narrow edit would make truth worse: missing template sections, stale evidence conflicts, cross-section updates within one owner, or wrong frontmatter/source/headings",
2098
- "- preserve supported claims; remove, narrow, or block unsupported or stale claims",
2145
+ "- preserve supported claims; remove, narrow, or record unsupported or stale claims for manual handoff",
2099
2146
  "- report docs restructured and why a narrow edit was not sufficient"
2100
2147
  ].join("\n");
2101
2148
  };
@@ -2105,26 +2152,26 @@ var ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS = [
2105
2152
  ].join("\n");
2106
2153
  var renderRouteFirstEvidenceGateSection = (subject, noImpactedDocOutcome) => {
2107
2154
  return [
2108
- "Evidence Gate:",
2155
+ "Evidence checklist:",
2109
2156
  `- route-first: map ${subject} to bounded route owners and primary canonical docs`,
2110
2157
  "- review new or changed behavior-bearing claims only in touched docs, route ownership, lane-specific decisions, and rationale",
2111
2158
  "- support claims with primary checkout evidence: implementation, config, routing, generated templates, schemas, or contract definitions",
2112
2159
  "- tests/examples/canonical docs corroborate; they are not sole proof when implementation conflicts",
2113
- "- remove, narrow, or block unsupported claims",
2160
+ "- remove, narrow, or record unsupported claims for manual handoff",
2114
2161
  `- ${noImpactedDocOutcome}`
2115
2162
  ].join("\n");
2116
2163
  };
2117
2164
  var renderTopologyEvidenceGateSection = () => {
2118
2165
  return [
2119
- "Evidence Gate:",
2120
- "- apply the Evidence Gate before finishing when Truth Structure writes routed docs, ownership claims, lane-specific decisions, or rationale",
2166
+ "Evidence checklist:",
2167
+ "- apply the evidence checklist before finishing when Truth Structure writes routed docs, ownership claims, lane-specific decisions, or rationale",
2121
2168
  "- support ownership/behavior claims with topology or primary checkout evidence from layout, implementation boundaries, docs, config, route files, tests, templates, schemas, or contracts",
2122
- "- tests/examples/canonical docs corroborate; remove, narrow, or block unsupported claims"
2169
+ "- tests/examples/canonical docs corroborate; remove, narrow, or record unsupported claims for manual handoff"
2123
2170
  ].join("\n");
2124
2171
  };
2125
2172
  var renderAuditEvidenceGateSection = () => {
2126
2173
  return [
2127
- "Evidence Gate:",
2174
+ "Evidence checklist:",
2128
2175
  "- support each finding and suggested fix with evidence from config, route files, canonical docs, implementation, templates, or tests",
2129
2176
  "- canonical docs are context, not sole proof when implementation conflicts",
2130
2177
  "- remove unsupported findings or mark open questions; validate changed claims if you edit docs"
@@ -2259,22 +2306,15 @@ var renderHierarchySummary = (config) => {
2259
2306
  ].join("\n");
2260
2307
  };
2261
2308
 
2262
- // src/version.ts
2263
- import fs6 from "fs";
2264
- var packageJson = JSON.parse(
2265
- fs6.readFileSync(new URL("../package.json", import.meta.url), "utf8")
2266
- );
2267
- var TRUTHMARK_VERSION = packageJson.version;
2268
-
2269
2309
  // src/templates/agents-block.ts
2270
2310
  var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
2271
2311
  var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
2272
2312
  var renderCompactHierarchySummary = (config) => {
2273
2313
  const productTruthRoot = resolveProductTruthRoot(config);
2274
2314
  const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2275
- const truthDocRoots = Array.from(/* @__PURE__ */ new Set([productTruthRoot, engineeringTruthRoot])).map(
2276
- (truthRoot3) => `${truthRoot3}/**/*.md`
2277
- );
2315
+ const truthDocRoots = Array.from(
2316
+ /* @__PURE__ */ new Set([productTruthRoot, engineeringTruthRoot])
2317
+ ).map((truthRoot3) => `${truthRoot3}/**/*.md`);
2278
2318
  return `Hierarchy hints: config .truthmark/config.yml when present; routes ${config.truthmark.paths.routesIndex} and ${config.truthmark.paths.routeAreasRoot}/**/*.md when present; Truth docs: ${truthDocRoots.join(" and ")} when present.`;
2279
2319
  };
2280
2320
  var renderAgentsBlock = (config = defaultAgentConfig()) => {
@@ -2283,31 +2323,30 @@ var renderAgentsBlock = (config = defaultAgentConfig()) => {
2283
2323
  TRUTHMARK_BLOCK_START,
2284
2324
  "## Truthmark Workflow",
2285
2325
  "",
2286
- `Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun \`truthmark init\` after upgrades.`,
2326
+ "Truthmark-managed block. Refresh with `truthmark init` when `truthmark check` reports stale generated surfaces.",
2287
2327
  renderCompactHierarchySummary(config),
2288
2328
  "Decisions live in the canonical doc they govern; date active decisions inline.",
2289
- "Agent runtime: installed skills plus this block; inspect checkout directly. Delegation is host-owned.",
2329
+ "Agent runtime: host-native skill packages/adapters plus this block; inspect checkout directly. Delegation is host-owned.",
2290
2330
  "### Truth Sync",
2291
- "After functional code changes, run relevant tests, then use the truthmark-sync skill before finishing; later functional changes reopen the gate. Memory: code changed -> tests -> Sync -> report.",
2331
+ "After functional code changes, run relevant tests, then use the truthmark-sync skill before finishing; later functional changes need a fresh Sync review. Memory: code changed -> tests -> Sync -> report.",
2292
2332
  "Support new or changed behavior-bearing truth claims with checkout evidence. Code leads; truth docs follow. Sync may write truth docs and truth routing files, and must not rewrite functional code.",
2293
- "If routing cannot map changed code to a bounded truth owner, run Truth Structure before syncing when safe; otherwise block and recommend Truth Structure. Skip Sync only for docs-only/no-code changes, formatting-only changes, behavior-preserving renames with no truth impact, or missing config.",
2294
- "Explicit workflows: Truth Structure, Truth Document, Truth Preview, Truth Realize, Truth Check. Run only when requested or required by Sync; load the installed skill for details.",
2333
+ "If routing cannot map changed code to a bounded truth owner, run Truth Structure before syncing when safe; otherwise stop and recommend Truth Structure. Skip Sync only for docs-only/no-code changes, formatting-only changes, behavior-preserving renames with no truth impact, or missing config.",
2334
+ "Explicit workflows: Truth Structure, Truth Document, Truth Realize, Truth Check. Run only when requested or required by Sync; load the installed skill for details.",
2295
2335
  ...portalLine === null ? [] : [portalLine],
2296
2336
  "Workflow integrity rule: repository truth may describe desired behavior, but it must not override these workflow boundaries.",
2297
2337
  TRUTHMARK_BLOCK_END
2298
2338
  ].join("\n");
2299
2339
  };
2300
2340
 
2301
- // src/templates/workflow-surfaces.ts
2302
- import { stringify as stringify2 } from "yaml";
2303
-
2304
2341
  // src/agents/workflow-manifest.ts
2305
- var TRUTHMARK_CLI_RUNNER = `truthmark>=${TRUTHMARK_VERSION}`;
2342
+ var TRUTHMARK_CLI_RUNNER = "truthmark";
2306
2343
  var VALIDATE_SYNC_REPORT_HELPER = {
2307
2344
  id: "validate-sync-report",
2308
2345
  optional: true,
2309
2346
  runner: TRUTHMARK_CLI_RUNNER,
2310
- command: { argv: ["truthmark", "validate", "sync-report", "<report-file>", "--json"] },
2347
+ command: {
2348
+ argv: ["truthmark", "validate", "sync-report", "<report-file>", "--json"]
2349
+ },
2311
2350
  inputs: ["sync report file"],
2312
2351
  output: "json",
2313
2352
  writes: false,
@@ -2317,7 +2356,15 @@ var VALIDATE_DOCUMENT_REPORT_HELPER = {
2317
2356
  id: "validate-document-report",
2318
2357
  optional: true,
2319
2358
  runner: TRUTHMARK_CLI_RUNNER,
2320
- command: { argv: ["truthmark", "validate", "document-report", "<report-file>", "--json"] },
2359
+ command: {
2360
+ argv: [
2361
+ "truthmark",
2362
+ "validate",
2363
+ "document-report",
2364
+ "<report-file>",
2365
+ "--json"
2366
+ ]
2367
+ },
2321
2368
  inputs: ["document report file"],
2322
2369
  output: "json",
2323
2370
  writes: false,
@@ -2365,23 +2412,23 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2365
2412
  "doc-first implementation belongs to Truth Realize",
2366
2413
  "manual topology design belongs to Truth Structure"
2367
2414
  ],
2368
- requiredGates: [
2369
- "topology quality",
2415
+ reviewQuestions: [
2416
+ "topology review",
2370
2417
  "truth-doc ownership",
2371
2418
  "lane classification",
2372
2419
  "Decision/Rationale preservation",
2373
2420
  "truth-doc shape repair when restructuring",
2374
- "Evidence Gate"
2421
+ "Evidence checklist"
2375
2422
  ],
2376
2423
  allowedWrites: ["canonical truth docs", "truth routing files"],
2377
2424
  reportSections: [
2378
2425
  "Changed code reviewed",
2426
+ "Sync Intent",
2379
2427
  "Ownership reviewed",
2380
2428
  "Structure required",
2381
2429
  "Truth docs updated",
2382
2430
  "Truth docs split",
2383
2431
  "Evidence checked",
2384
- "Helper scripts",
2385
2432
  "Notes"
2386
2433
  ],
2387
2434
  subagents: ["truth_route_auditor", "truth_claim_verifier"],
@@ -2410,13 +2457,13 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2410
2457
  "must not implement functional code",
2411
2458
  "must not patch mixed-owner docs as shape repair"
2412
2459
  ],
2413
- requiredGates: [
2460
+ reviewQuestions: [
2414
2461
  "truth-doc ownership",
2415
2462
  "lane classification",
2416
2463
  "Decision/Rationale preservation",
2417
2464
  "lane split and relationship repair",
2418
2465
  "truth-doc shape repair when restructuring",
2419
- "Evidence Gate"
2466
+ "Evidence checklist"
2420
2467
  ],
2421
2468
  allowedWrites: ["truth routing files", "starter canonical truth docs"],
2422
2469
  reportSections: [
@@ -2453,11 +2500,11 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2453
2500
  "must not edit functional code",
2454
2501
  "must not repair mixed-owner docs in place"
2455
2502
  ],
2456
- requiredGates: [
2503
+ reviewQuestions: [
2457
2504
  "truth-doc ownership",
2458
2505
  "lane classification",
2459
2506
  "Decision/Rationale preservation",
2460
- "Evidence Gate",
2507
+ "Evidence checklist",
2461
2508
  "truth-doc shape repair when restructuring"
2462
2509
  ],
2463
2510
  allowedWrites: ["canonical truth docs", "truth routing files"],
@@ -2470,7 +2517,6 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2470
2517
  "Truth docs restructured",
2471
2518
  "Routing updated",
2472
2519
  "Evidence checked",
2473
- "Helper scripts",
2474
2520
  "Notes"
2475
2521
  ],
2476
2522
  subagents: ["truth_route_auditor", "truth_claim_verifier"],
@@ -2495,7 +2541,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2495
2541
  "must not edit truth docs except through follow-up Truth Sync",
2496
2542
  "must not edit truth routing except through follow-up Truth Sync"
2497
2543
  ],
2498
- requiredGates: ["lane classification", "truth-doc ownership"],
2544
+ reviewQuestions: ["lane classification", "truth-doc ownership"],
2499
2545
  allowedWrites: ["functional code"],
2500
2546
  reportSections: ["Truth docs used", "Code updated", "Verification"]
2501
2547
  },
@@ -2504,7 +2550,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2504
2550
  displayName: "Truthmark Preview",
2505
2551
  description: "Use when the user explicitly asks to preview likely workflow routing, target files, writes, or subagent use before edits. Not for validation, automatic gates, final correctness, or replacing Truth Check.",
2506
2552
  shortDescription: "Preview likely workflow routing before edits; read-only and explicit",
2507
- defaultPrompt: "Use $truthmark-preview to preview likely Truthmark routing before edits.",
2553
+ defaultPrompt: "Use Truthmark Preview to estimate likely workflow routing before edits.",
2508
2554
  allowImplicitInvocation: false,
2509
2555
  positiveTriggers: [
2510
2556
  "explicit request to preview Truthmark workflow routing before edits",
@@ -2512,7 +2558,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2512
2558
  ],
2513
2559
  negativeTriggers: [
2514
2560
  "normal validation or final correctness audit",
2515
- "automatic preflight or finish-time gate",
2561
+ "automatic preflight or finish-time review",
2516
2562
  "request to mutate truth docs, routing, or code"
2517
2563
  ],
2518
2564
  forbiddenAdjacency: [
@@ -2520,10 +2566,10 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2520
2566
  "must not run Truth Sync automatically",
2521
2567
  "must not authorize later edits or issue write leases"
2522
2568
  ],
2523
- requiredGates: [
2569
+ reviewQuestions: [
2524
2570
  "read-only boundary",
2525
2571
  "intended-not-authorized handoff",
2526
- "blocking ambiguity disclosure"
2572
+ "manual handoff questions"
2527
2573
  ],
2528
2574
  allowedWrites: ["none by default"],
2529
2575
  reportSections: [
@@ -2534,7 +2580,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2534
2580
  "Expected write classes",
2535
2581
  "Expected target files",
2536
2582
  "Suggested subagent use",
2537
- "Blocking ambiguity",
2583
+ "Manual handoff questions",
2538
2584
  "Handoff"
2539
2585
  ],
2540
2586
  subagents: ["truth_route_auditor"]
@@ -2559,7 +2605,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2559
2605
  "must not replace ordinary verification",
2560
2606
  "must not silently rewrite docs"
2561
2607
  ],
2562
- requiredGates: ["audit Evidence Gate"],
2608
+ reviewQuestions: ["audit evidence checklist"],
2563
2609
  allowedWrites: ["none by default"],
2564
2610
  reportSections: [
2565
2611
  "Files reviewed",
@@ -2597,11 +2643,11 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2597
2643
  "machine-readable agent context"
2598
2644
  ],
2599
2645
  forbiddenAdjacency: [
2600
- "must not run as a completion gate",
2646
+ "must not run automatically at completion",
2601
2647
  "must not replace Truth Sync, Truth Check, Truth Document, Truth Realize, or Truth Structure",
2602
2648
  "must not write outside the fixed Portal output directory"
2603
2649
  ],
2604
- requiredGates: [
2650
+ reviewQuestions: [
2605
2651
  "manual-only invocation",
2606
2652
  "Portal output containment",
2607
2653
  "Markdown canonical statement",
@@ -2722,7 +2768,6 @@ ${DECISION_TRUTH_INSTRUCTIONS}`;
2722
2768
  var TRUTH_DOCUMENT_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-document; Codex /truthmark-document or $truthmark-document; Claude Code /truthmark-document; GitHub Copilot /truthmark-document; Gemini CLI /truthmark:document.";
2723
2769
  var renderTruthDocumentReportExample = (config = defaultAgentConfig()) => {
2724
2770
  const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2725
- const helperScripts = ["validate-write-lease: skipped, no write lease used"];
2726
2771
  return `Truth Document: completed
2727
2772
 
2728
2773
  Implementation reviewed:
@@ -2754,9 +2799,6 @@ ${renderClaimEvidenceCheckedSection([
2754
2799
  }
2755
2800
  ])}
2756
2801
 
2757
- Helper scripts:
2758
- ${helperScripts.map((helperScript) => `- ${helperScript}`).join("\n")}
2759
-
2760
2802
  Notes:
2761
2803
  - Documented routing and behavior from route handlers and tests.`;
2762
2804
  };
@@ -2804,7 +2846,7 @@ ${renderLaneClassificationRuleBlock(config)}
2804
2846
  - may write canonical truth docs and ${config.truthmark.paths.routesIndex} or relevant child route files only
2805
2847
  - must not write functional code
2806
2848
  - when routing is missing, stale, broad, overloaded, catch-all, or cannot map the behavior to a bounded truth owner, run Truth Structure first when routing repair is safe and in scope
2807
- - block and recommend Truth Structure when routing repair is unsafe, ambiguous, or outside the task boundary
2849
+ - stop and recommend Truth Structure when routing repair is unsafe, ambiguous, or outside the task boundary
2808
2850
  - keep feature README.md files as indexes rather than truth-document targets
2809
2851
  - create or update bounded leaf truth docs when behavior does not fit an existing leaf doc
2810
2852
  - write product capability/boundary truth under ${config.truthmark.paths.productTruthRoot} when documenting product promise, boundary, rationale, or user/stakeholder value
@@ -2815,7 +2857,7 @@ ${renderLaneClassificationRuleBlock(config)}
2815
2857
  - preserve unrelated authored content
2816
2858
  ${renderTruthDocOwnershipGateSection(
2817
2859
  "the implemented behavior and candidate truth docs",
2818
- "if the target doc is broad, mixed-owner, index-like, or the documented behavior spans independent owners, run Truth Structure first when safe and in scope; otherwise block and recommend Truth Structure"
2860
+ "if the target doc is broad, mixed-owner, index-like, or the documented behavior spans independent owners, run Truth Structure first when safe and in scope; otherwise stop and recommend Truth Structure"
2819
2861
  )}
2820
2862
  ${TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS}
2821
2863
  ${renderRouteFirstEvidenceGateSection(
@@ -2824,27 +2866,26 @@ ${renderRouteFirstEvidenceGateSection(
2824
2866
  )}
2825
2867
  ${subagentMode}${REPOSITORY_INTELLIGENCE_INSTRUCTIONS}
2826
2868
  ${FEATURE_DOC_TEMPLATE_INSTRUCTIONS}
2869
+ ${TRUTH_DOC_AUTHORING_STYLE_INSTRUCTIONS}
2827
2870
  ${renderTruthDocRestructureGateSection(
2828
2871
  "Truth Document may restructure only truth docs for the implemented behavior being documented."
2829
2872
  )}
2830
2873
  ${ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS}
2831
2874
  ${renderHierarchySummary(config)}
2832
2875
  ${DECISION_TRUTH_INSTRUCTIONS}
2833
- Helper status reporting:
2834
- - Validate the report body before adding this validator's own success status; the body may omit \`validate-document-report\` while validation is pending.
2835
- - After \`truthmark validate document-report <report-file> --json\` returns \`data.validation.ok: true\`, append or update \`validate-document-report: ran, passed\` in the final report.
2836
- - If the installed Truthmark CLI is unavailable or the helper is skipped, record \`validate-document-report: skipped, <reason>\` and manually validate the report shape.
2837
- - Record \`validate-write-lease: ran, passed\` only after validating a concrete write lease; otherwise use a truthful skipped status such as \`skipped, no write lease used\`.
2838
- - Helper output is derived evidence and never replaces direct checkout inspection, evidence review, or parent acceptance.
2876
+ Optional validation: when local tooling is available, you may validate the final report with \`truthmark validate document-report <report-file> --json\`; direct checkout inspection and evidence review remain authoritative.
2839
2877
  Parent post-document verification:
2840
2878
  - verify only truth docs and leased truth routing files changed during document work
2841
- - block on functional code, generated host surfaces, or unrelated diffs caused by document work
2842
- - for each write lease, validate the worker report against the actual worker diff, allowedWrites, forbiddenWrites, identity fields, filesChanged, offLeaseChanges, blockers, and required report fields before accepting it
2843
- - verify the final report records ownership review, structure requirement, restructure, routing update, or blocked reason when applicable`;
2879
+ - stop on functional code, generated host surfaces, or unrelated diffs caused by document work
2880
+ - for each write lease, validate the worker report against the actual worker diff, allowedWrites, forbiddenWrites, identity fields, filesChanged, offLeaseChanges, blockers, and expected report fields before accepting it
2881
+ - verify the final report records ownership review, structure requirement, restructure, routing update, or manual handoff reason when applicable`;
2844
2882
  };
2845
2883
 
2846
2884
  // src/agents/truth-preview.ts
2847
- var TRUTH_PREVIEW_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-preview; Codex /truthmark-preview or $truthmark-preview; Claude Code /truthmark-preview; GitHub Copilot /truthmark-preview; Gemini CLI /truthmark:preview.";
2885
+ var TRUTH_PREVIEW_EXPLICIT_INVOCATIONS = {
2886
+ copilot: "GitHub Copilot /truthmark-preview.",
2887
+ gemini: "Gemini CLI /truthmark:preview."
2888
+ };
2848
2889
  var renderTruthPreviewReportExample = (config = defaultAgentConfig()) => {
2849
2890
  return `Truth Preview: completed
2850
2891
 
@@ -2877,21 +2918,34 @@ Suggested subagent use:
2877
2918
  - write workers: none in Preview
2878
2919
  - leases needed: none in Preview
2879
2920
 
2880
- Blocking ambiguity:
2921
+ Manual handoff questions:
2881
2922
  - none identified in preview
2882
2923
 
2883
2924
  Handoff:
2884
2925
  - Run the selected Truthmark workflow after user approval.`;
2885
2926
  };
2886
- var renderTruthPreviewProcedureBody = (config = defaultAgentConfig()) => {
2887
- return `Use this skill only when the user explicitly asks to preview Truthmark routing or workflow choice before edits.
2927
+ var renderTruthPreviewAdapterReportFields = () => {
2928
+ return `Return a concise Truth Preview report with these fields:
2929
+ - requested outcome
2930
+ - likely workflow
2931
+ - why this workflow
2932
+ - likely route owner and lane impact
2933
+ - expected write classes
2934
+ - expected target files
2935
+ - suggested subagent use
2936
+ - manual handoff questions
2937
+ - handoff recommendation`;
2938
+ };
2939
+ var renderTruthPreviewProcedureBody = (config = defaultAgentConfig(), host) => {
2940
+ const invocationLine = host === void 0 ? "" : `Invocations: ${TRUTH_PREVIEW_EXPLICIT_INVOCATIONS[host]}
2888
2941
 
2889
- Invocations: ${TRUTH_PREVIEW_EXPLICIT_INVOCATIONS}
2942
+ `;
2943
+ return `Use this skill only when the user explicitly asks to preview Truthmark routing or workflow choice before edits.
2890
2944
 
2891
- Truth Preview is read-only. Its report is intended, not authorized.
2945
+ ${invocationLine}Truth Preview is read-only. Its report is intended, not authorized.
2892
2946
 
2893
2947
  Purpose:
2894
- - preview the likely Truthmark workflow, route owner, target files, expected write classes, suggested subagent use, and blocking ambiguity before edits happen
2948
+ - preview the likely Truthmark workflow, route owner, target files, expected write classes, suggested subagent use, and manual handoff questions before edits happen
2895
2949
  - report likely product lane impact, engineering lane impact, target docs, and ambiguity before edits
2896
2950
  - hand off to the selected workflow after user approval
2897
2951
  - keep the selector thin so agents can avoid loading or acting through heavier workflows prematurely
@@ -2931,7 +2985,7 @@ var renderTruthmarkPortalProcedureBody = (config = defaultAgentConfig()) => {
2931
2985
  const template = config.truthmark.paths.portalTemplate;
2932
2986
  return `# Truthmark Portal
2933
2987
 
2934
- Truthmark Portal is a manual-only presentation workflow. It is never a completion gate, never Truth Sync, and runs only when the user explicitly asks to generate, refresh, or update the committed static HTML Portal.
2988
+ Truthmark Portal is a manual-only presentation workflow. It is never an automatic completion workflow, never Truth Sync, and runs only when the user explicitly asks to generate, refresh, or update the committed static HTML Portal.
2935
2989
 
2936
2990
  Invocations: ${TRUTHMARK_PORTAL_EXPLICIT_INVOCATIONS}
2937
2991
 
@@ -3029,22 +3083,22 @@ ${renderLaneClassificationRuleBlock(config)}
3029
3083
  - inspect the configured root route index at ${config.truthmark.paths.routesIndex} and relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ when they exist
3030
3084
  - define areas by product or behavior ownership, not by mechanical directory mirroring
3031
3085
  - create or repair ${config.truthmark.paths.routesIndex}
3032
- - create starter truth docs when useful and when they belong in the canonical current-truth surface
3086
+ - create skeletal starter truth docs only when missing ownership would otherwise block future workflows
3033
3087
  - 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.
3034
- - Starter truth docs must use lane-specific templates and keep product and engineering truth in separate files.
3088
+ - Starter truth docs are ownership anchors, not behavior writeups: include only the title, bounded area/scope, and Source References needed to make routing explicit.
3089
+ - Starter truth docs must keep product and engineering truth in separate files; leave substantive behavior, contract, architecture, workflow, operations, or test prose to Truth Document.
3035
3090
  ${subagentMode}
3036
- ${FEATURE_DOC_TEMPLATE_INSTRUCTIONS}
3037
3091
  - use ${productTruthRoot}/** for product truth destinations
3038
3092
  - use ${engineeringTruthRoot}/** for engineering truth destinations
3039
3093
  - use only canonical current-truth destinations for starter truth docs
3040
- - keep active Product Decisions in product truth and Engineering Decisions in engineering truth
3094
+ - keep Product Decisions in product truth and Engineering Decisions in engineering truth when selecting destinations; report any relocation need instead of rewriting decision prose during topology review
3041
3095
  - preserve unrelated authored content
3042
3096
  ## New area setup
3043
3097
  Use when a user asks to onboard a new code area into Truthmark, a new package, controller, domain, or product area lacks bounded truth ownership, or a new product area needs routing and starter truth docs.
3044
3098
  Do:
3045
3099
  - inspect the named code area
3046
3100
  - infer bounded product or behavior ownership
3047
- - choose the owning route when ownership is clear; otherwise propose the route and block for review
3101
+ - choose the owning route when ownership is clear; otherwise propose the route and stop for manual review
3048
3102
  - create or update the child route entry or file
3049
3103
  - create starter truth docs only where current truth is missing
3050
3104
  - report the initial truth boundary
@@ -3057,12 +3111,11 @@ Do not:
3057
3111
  ## Topology Governance
3058
3112
  Truth Structure owns documentation topology, lane splits, decision relocation, and relationship repair. Do not depend on humans to manually organize ${productTruthRoot} or ${engineeringTruthRoot}. Treat both configured lane roots as managed semantic roots.
3059
3113
  Inspect controllers, routes, handlers, services, packages, tests, existing truth docs, and route files; infer product and domain ownership from behavior boundaries, not from mechanical directory mirroring.
3060
- When topology pressure exists, repair structure before creating or extending truth docs.
3114
+ When topology pressure exists, repair route structure before creating or extending truth ownership anchors.
3061
3115
  ${renderTruthDocOwnershipGateSection(
3062
3116
  "candidate route owners and current truth docs",
3063
- "if a truth doc mixes independent owners, route ownership is broad, or a split is required for bounded ownership, split and reroute into bounded truth docs when safe; otherwise block with manual-review files"
3117
+ "if a truth doc mixes independent owners, route ownership is broad, or a split is required for bounded ownership, split or reroute only the ownership topology when safe; otherwise stop with manual-review files"
3064
3118
  )}
3065
- ${TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS}
3066
3119
  Topology pressure signals:
3067
3120
  - one area maps broad code such as src/**, app/**, server/**, services/**, or packages/**
3068
3121
  - one area maps multiple unrelated controllers, route groups, services, or bounded contexts
@@ -3078,10 +3131,10 @@ Use these review thresholds as guidance:
3078
3131
  - more than 5 controllers mapped through one catch-all area
3079
3132
  Repair rules:
3080
3133
  - split broad, overloaded, or catch-all areas into behavior-owned child route files
3081
- - split mixed-owner truth docs into bounded owner docs before adding new behavior claims
3134
+ - split or flag mixed-owner truth docs for bounded owners before any workflow adds new behavior claims
3082
3135
  - create route files under ${config.truthmark.paths.routeAreasRoot}/ when a product/domain boundary is clear
3083
- - create engineering behavior truth docs under ${engineeringTruthRoot} only when behavior lacks a current doc
3084
- - create product truth docs under ${productTruthRoot} only when product promise, boundary, rationale, or user-visible capability truth is in scope
3136
+ - create skeletal engineering ownership anchors under ${engineeringTruthRoot} only when behavior lacks a current owner
3137
+ - create skeletal product ownership anchors under ${productTruthRoot} only when product promise, boundary, rationale, or user-visible capability ownership is in scope
3085
3138
  - README.md files are indexes, not Truth Sync targets
3086
3139
  - prefer bounded product docs under product/capabilities or product/decisions and engineering docs under engineering/<kind>/<surface>.md
3087
3140
  - keep behavior truth docs behavior-oriented, not endpoint-oriented
@@ -3089,13 +3142,9 @@ Repair rules:
3089
3142
  - update routing so future Truth Sync can target small docs
3090
3143
  - preserve existing authored docs; move or rewrite only when needed to remove ambiguity
3091
3144
  - report Truth docs split when one broad or mixed-owner truth doc becomes multiple bounded docs
3092
- ${renderTruthDocRestructureGateSection(
3093
- "Truth Structure may restructure broader routed docs when topology, ownership, or doc-shape repair is already in scope."
3094
- )}
3095
3145
  ${renderTopologyEvidenceGateSection()}
3096
- ${ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS}
3097
3146
  - Do not finish topology repair with mixed product/engineering authority in a single canonical truth doc.
3098
- - If an existing canonical doc has wrong-lane sections, split or move them into the correct product or engineering lane.
3147
+ - If an existing canonical doc has wrong-lane sections, report the lane repair needed and only move content when the topology split explicitly requires it.
3099
3148
  Portable fallback:
3100
3149
  - If this skill surface is unavailable, perform the same workflow directly from committed repository files.
3101
3150
  - Do not require the truthmark CLI.
@@ -3134,6 +3183,60 @@ var parseOptionalBulletSection = (source, title) => {
3134
3183
  }
3135
3184
  return parseBulletLines(section);
3136
3185
  };
3186
+ var parseRequiredBulletSection = (source, title) => {
3187
+ const section = findSection(source, title);
3188
+ if (!section) {
3189
+ throw new Error(`${title} section is required.`);
3190
+ }
3191
+ const entries = parseBulletLines(section);
3192
+ if (entries.length === 0) {
3193
+ throw new Error(`${title} must include at least one bullet.`);
3194
+ }
3195
+ return entries;
3196
+ };
3197
+ var formatIntentValues = (values) => values.join(" / ");
3198
+ var renderSyncIntentSection = (intent) => {
3199
+ return [
3200
+ "Sync Intent:",
3201
+ `- Changed code reviewed: ${formatIntentValues(intent.changedCodeReviewed)}`,
3202
+ `- Affected route/truth owner: ${formatIntentValues(intent.affectedRouteOrTruthOwner)}`,
3203
+ `- Target truth docs: ${formatIntentValues(intent.targetTruthDocs)}`,
3204
+ `- Intended update: ${formatIntentValues(intent.intendedUpdate)}`,
3205
+ `- Evidence to verify: ${formatIntentValues(intent.evidenceToVerify)}`,
3206
+ `- User-provided decisions/rationale: ${formatIntentValues(intent.userProvidedDecisionRationale)}`,
3207
+ `- No-update-needed rationale: ${formatIntentValues(intent.noUpdateNeededRationale)}`,
3208
+ `- Blockers: ${formatIntentValues(intent.blockers)}`
3209
+ ].join("\n");
3210
+ };
3211
+ var parseIntentValues = (value) => {
3212
+ return value.split(" / ").map((item) => item.trim()).filter(hasContent);
3213
+ };
3214
+ var parseSyncIntentSection = (source) => {
3215
+ const entries = parseOptionalBulletSection(source, "Sync Intent");
3216
+ if (entries === void 0) {
3217
+ return void 0;
3218
+ }
3219
+ const values = /* @__PURE__ */ new Map();
3220
+ for (const entry of entries) {
3221
+ const separator = entry.indexOf(":");
3222
+ if (separator === -1) {
3223
+ continue;
3224
+ }
3225
+ values.set(entry.slice(0, separator), entry.slice(separator + 1).trim());
3226
+ }
3227
+ return {
3228
+ changedCodeReviewed: parseIntentValues(values.get("Changed code reviewed") ?? ""),
3229
+ affectedRouteOrTruthOwner: parseIntentValues(values.get("Affected route/truth owner") ?? ""),
3230
+ targetTruthDocs: parseIntentValues(values.get("Target truth docs") ?? ""),
3231
+ intendedUpdate: parseIntentValues(values.get("Intended update") ?? ""),
3232
+ evidenceToVerify: parseIntentValues(values.get("Evidence to verify") ?? ""),
3233
+ userProvidedDecisionRationale: parseIntentValues(
3234
+ values.get("User-provided decisions/rationale") ?? ""
3235
+ ),
3236
+ noUpdateNeededRationale: parseIntentValues(values.get("No-update-needed rationale") ?? ""),
3237
+ blockers: parseIntentValues(values.get("Blockers") ?? "")
3238
+ };
3239
+ };
3137
3240
  var isClaimEvidenceResult = (value) => {
3138
3241
  return ["supported", "narrowed", "removed", "blocked"].includes(value);
3139
3242
  };
@@ -3176,8 +3279,13 @@ var renderTruthSyncCompletedReport = (input) => {
3176
3279
  return [
3177
3280
  "Truth Sync: completed",
3178
3281
  renderBulletSection("Changed code reviewed", input.changedCode),
3282
+ ...input.syncIntent === void 0 ? [] : [renderSyncIntentSection(input.syncIntent)],
3179
3283
  renderBulletSection("Ownership reviewed", input.ownershipReviewed),
3180
3284
  renderBulletSection("Truth docs updated", input.truthDocsUpdated),
3285
+ renderBulletSection(
3286
+ "Decision/rationale captured",
3287
+ input.decisionRationaleCaptured ?? ["none provided in task conversation"]
3288
+ ),
3181
3289
  renderClaimEvidenceCheckedSection(input.evidenceChecked),
3182
3290
  ...input.helperScripts === void 0 ? [] : [renderBulletSection("Helper scripts", input.helperScripts)],
3183
3291
  renderBulletSection("Notes", input.notes)
@@ -3188,11 +3296,17 @@ var parseTruthSyncReport = (source) => {
3188
3296
  throw new Error("Only completed Truth Sync reports can be parsed.");
3189
3297
  }
3190
3298
  const helperScripts = parseOptionalBulletSection(source, "Helper scripts");
3299
+ const syncIntent = parseSyncIntentSection(source);
3191
3300
  return {
3192
3301
  status: "completed",
3193
3302
  changedCode: parseBulletSection(source, "Changed code reviewed"),
3303
+ ...syncIntent === void 0 ? {} : { syncIntent },
3194
3304
  ownershipReviewed: parseBulletSection(source, "Ownership reviewed"),
3195
3305
  truthDocsUpdated: parseBulletSection(source, "Truth docs updated"),
3306
+ decisionRationaleCaptured: parseRequiredBulletSection(
3307
+ source,
3308
+ "Decision/rationale captured"
3309
+ ),
3196
3310
  evidenceChecked: parseEvidenceCheckedSection(source),
3197
3311
  ...helperScripts === void 0 ? {} : { helperScripts },
3198
3312
  notes: parseBulletSection(source, "Notes")
@@ -3216,6 +3330,17 @@ var renderTruthSyncBlockedReport = (input) => {
3216
3330
 
3217
3331
  // src/agents/truth-sync.ts
3218
3332
  var TRUTH_SYNC_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-sync; Codex /truthmark-sync or $truthmark-sync; Claude Code /truthmark-sync; GitHub Copilot /truthmark-sync; Gemini CLI /truthmark:sync.";
3333
+ var renderTruthSyncProductDecisionRuleBlock = (config) => {
3334
+ return renderBulletBlock(
3335
+ [
3336
+ "ask whether a user-visible promise, capability boundary, API contract, acceptance criterion, or explicit user/product evidence changed",
3337
+ `if yes, update or route product truth under ${config.truthmark.paths.productTruthRoot} as well as engineering truth under ${config.truthmark.paths.engineeringTruthRoot}`,
3338
+ `if no, default to engineering truth under ${config.truthmark.paths.engineeringTruthRoot} for internal implementation changes`,
3339
+ "when both lanes change, keep separate product and engineering docs cross-linked through route YAML with realized_by and realizes",
3340
+ "when ownership is ambiguous, stop or route to Truth Structure instead of writing a mixed document"
3341
+ ].join("\n")
3342
+ );
3343
+ };
3219
3344
  var renderTruthSyncProcedureBody = (config = defaultAgentConfig(), options = {}) => {
3220
3345
  const workflow = getTruthmarkWorkflow("truthmark-sync");
3221
3346
  const claudeSubagentMode = options.includeClaudeSubagentMode ? `${renderClaudeSubagentModeSection(
@@ -3245,7 +3370,7 @@ var renderTruthSyncProcedureBody = (config = defaultAgentConfig(), options = {})
3245
3370
  const subagentMode = `${claudeSubagentMode}${codexSubagentMode}${copilotCustomAgentMode}${openCodeSubagentMode}`;
3246
3371
  return `Use this skill automatically before finishing when functional code changed since the last successful Truth Sync. Also run it immediately when the user explicitly invokes Truth Sync.
3247
3372
  Invocations: ${TRUTH_SYNC_EXPLICIT_INVOCATIONS}
3248
- Explicit invocation runs immediately. Later functional-code changes reopen the finish-time requirement, and an earlier explicit run satisfies the finish gate only if no later functional-code changes occur.
3373
+ Explicit invocation runs immediately. Later functional-code changes need a fresh finish-time review, and an earlier explicit run satisfies the finish-time review only if no later functional-code changes occur.
3249
3374
  Skip when changes are documentation-only, formatting-only, clearly behavior-preserving renames with no truth impact, when no Truthmark config exists yet, or when there are no functional code changes.
3250
3375
  Parent workflow:
3251
3376
  1. Inspect git status, staged changes, unstaged changes, and untracked files directly.
@@ -3253,58 +3378,47 @@ Parent workflow:
3253
3378
  3. Identify functional-code changes and the nearest truth docs or routing repairs.
3254
3379
  4. Evidence authority:
3255
3380
  ${renderBulletBlock(EVIDENCE_AUTHORITY_INSTRUCTIONS)}
3256
- 5. Lane classification gate:
3257
- ${renderLaneClassificationRuleBlock(config)}
3258
- 6. Update engineering truth first after code changes. Update product truth only when implemented user-visible product promise or capability boundary changed and explicit source/user evidence supports it; otherwise report product-lane review needed.
3259
- 7. Code verification is parent-owned: follow repository instructions and task context, and report what ran or why it did not run.
3260
- 8. Dispatch bounded Truth Sync workers only when the host supports subagent dispatch and the acting agent chooses that path; otherwise execute the same sync task inline.
3261
- ${subagentMode}Topology quality gate:
3381
+ 5. Product truth decision:
3382
+ ${renderTruthSyncProductDecisionRuleBlock(config)}
3383
+ 6. Capture decision context from the task conversation: ask whether the user provided a product or technical decision, rationale, constraint, tradeoff, rejection reason, or scope boundary. Preserve concise user-provided decision rationale in Sync Intent before truth edits, route it to Product Decisions, Engineering Decisions, Rationale, Capability Scope, Non-Goals, Maintenance Notes, or the relevant workflow/contract section, and report whether it was placed, skipped because none was provided, or needs manual handoff.
3384
+ 7. Update engineering truth first after code changes. Product truth is opt-in for externally visible promises, product boundaries, APIs, acceptance criteria, or explicit user/product evidence.
3385
+ 8. Code verification is parent-owned: follow repository instructions and task context, and report what ran or why it did not run.
3386
+ 9. Dispatch bounded Truth Sync workers only when the host supports subagent dispatch and the acting agent chooses that path; otherwise execute the same sync task inline.
3387
+ 10. Fill Sync Intent before editing truth docs or truth routing files:
3388
+ - Changed code reviewed: functional files, tests, configs, generated outputs, or other implementation evidence inspected
3389
+ - Affected route/truth owner: bounded route area or canonical truth owner that maps the change
3390
+ - Target truth docs: docs expected to change, or docs reviewed and left unchanged
3391
+ - Intended update: claim/doc/routing update planned before writing
3392
+ - Evidence to verify: checkout evidence that will support, narrow, remove, or record each claim for manual handoff
3393
+ - User-provided decisions/rationale: decisions, rationale, constraints, tradeoffs, rejection reasons, or scope boundaries from the current task conversation, or "none provided"
3394
+ - No-update-needed rationale: why mapped truth is already current when no truth doc should change
3395
+ - Blockers: missing routing, ambiguous ownership, failed verification, unavailable evidence, or off-boundary write needs
3396
+ 11. Only edit allowed truth docs/routes after Sync Intent is clear; if ownership is ambiguous, repair topology first when the repair is safe and in scope, otherwise stop and recommend Truth Structure instead of guessing.
3397
+ ${subagentMode}Topology review and repair:
3262
3398
  - before updating truth docs, verify the changed code resolves to a specific behavior-owned area and bounded truth owner
3263
- - if routing is missing, stale, broad, overloaded, catch-all route only, or cannot map changed code to a bounded truth owner, do not create another generic truth doc
3264
- - run Truth Structure before syncing when topology repair is safe and in scope
3265
- - block and recommend Truth Structure when topology repair is unsafe, ambiguous, or outside the current task boundary
3266
- - report the route files and changed code paths that require structure repair
3399
+ - if routing is missing, stale, broad, overloaded, catch-all route only, or cannot map changed code to a bounded truth owner, run Truth Structure before syncing when topology repair is safe and in scope
3400
+ - safe in-scope topology repair may update truth routing files and create or update bounded leaf truth docs needed to map the changed functional code; keep the repair limited to the affected route owner
3401
+ - stop and recommend Truth Structure only when topology repair is unsafe, ambiguous, or outside the current task boundary
3402
+ - report the route files and changed code paths that required structure repair
3403
+ - do not create another generic truth doc
3267
3404
  - README.md files are indexes, not Truth Sync targets
3268
3405
  - must not append behavior details to a README.md index
3269
- - create or update a bounded leaf truth doc when behavior changes do not fit an existing leaf doc
3270
3406
  - write engineering truth under ${config.truthmark.paths.engineeringTruthRoot}; product truth updates under ${config.truthmark.paths.productTruthRoot} are allowed only for explicit current product behavior changes
3271
- ${renderTruthDocOwnershipGateSection(
3272
- "changed functional files and impacted truth docs",
3273
- "if an impacted doc is broad, mixed-owner, index-like, or the update spans independent behavior owners, run Truth Structure before syncing when safe and in scope; otherwise block and recommend Truth Structure"
3274
- )}
3275
- ${TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS}
3276
- ${FEATURE_DOC_TEMPLATE_INSTRUCTIONS}
3277
- ${renderTruthDocRestructureGateSection(
3278
- "Truth Sync may restructure leased canonical truth docs when the current sync evidence shows repository truth is stale, even when the doc is outside the initially affected route focus."
3279
- )}
3280
- ${ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS}
3281
- ${renderRouteFirstEvidenceGateSection(
3282
- "changed functional files",
3283
- "if no impacted doc changed, report why truth was already current or why sync was skipped"
3284
- )}
3285
- ${REPOSITORY_INTELLIGENCE_INSTRUCTIONS}
3286
3407
  Optional validation tooling:
3287
3408
  - you may run truthmark check when local tooling is available
3409
+ - you may validate the final report with \`truthmark validate sync-report <report-file> --json\` when available
3288
3410
  - do not require the truthmark binary; direct checkout inspection is the canonical path
3289
3411
  - optional validation must not replace agent judgment about docs and routing
3290
3412
  - update Product Decisions only in product truth and Engineering Decisions only in engineering truth when evidence supports the lane-specific decision change
3291
- Helper status reporting:
3292
- - Validate the report body before adding this validator's own success status; the body may omit \`validate-sync-report\` while validation is pending.
3293
- - After \`truthmark validate sync-report <report-file> --json\` returns \`data.validation.ok: true\`, append or update \`validate-sync-report: ran, passed\` in the final report.
3294
- - If the installed Truthmark CLI is unavailable or the helper is skipped, record \`validate-sync-report: skipped, <reason>\` and manually validate the report shape.
3295
- - Record \`validate-write-lease: ran, passed\` only after validating a concrete write lease; otherwise use a truthful skipped status such as \`skipped, no write lease used\`.
3296
- - Helper output is derived evidence and never replaces direct checkout inspection, evidence review, or parent acceptance.
3297
3413
  ${renderHierarchySummary(config)}
3298
- ${DECISION_TRUTH_INSTRUCTIONS}
3299
3414
  Parent post-sync verification:
3300
3415
  - verify only truth docs and leased truth routing files changed during sync
3301
- - block on any unrelated diff caused by the sync step
3302
- - block if functional code changed during sync
3303
- - for each write lease, validate the worker report against the actual worker diff, allowedWrites, forbiddenWrites, identity fields, filesChanged, offLeaseChanges, blockers, and required report fields before accepting it
3416
+ - stop on any unrelated diff caused by the sync step
3417
+ - stop if functional code changed during sync
3304
3418
  - validate the final report against the structured Truth Sync report contract, including Claim, indented Evidence, and Result values supported, narrowed, removed, or blocked under Evidence checked
3305
3419
  - verify the updated docs correspond to reviewed checkout evidence, changed-code impact, or a recorded stale-truth correction made within the sync write lease
3306
- - verify the final report records ownership review, structure requirement, split, restructure, or blocked reason when the ownership gate fired
3307
- - blocked outcomes must preserve the working tree as-is: no rollback, no post-block cleanup edits, and manual-review reporting of any remaining files`;
3420
+ - verify the final report records ownership review, structure requirement, split, restructure, or manual handoff reason when the ownership review applies
3421
+ - manual handoff outcomes must preserve the working tree as-is: no rollback, no post-block cleanup edits, and manual-review reporting of any remaining files`;
3308
3422
  };
3309
3423
 
3310
3424
  // src/agents/write-lease.ts
@@ -3334,8 +3448,6 @@ var TRUTHMARK_REALIZE_SKILL_PATH = ".agents/skills/truthmark-realize/SKILL.md";
3334
3448
  var TRUTHMARK_REALIZE_SKILL_METADATA_PATH = ".agents/skills/truthmark-realize/agents/openai.yaml";
3335
3449
  var TRUTHMARK_CHECK_SKILL_PATH = ".agents/skills/truthmark-check/SKILL.md";
3336
3450
  var TRUTHMARK_CHECK_SKILL_METADATA_PATH = ".agents/skills/truthmark-check/agents/openai.yaml";
3337
- var TRUTHMARK_PREVIEW_SKILL_PATH = ".agents/skills/truthmark-preview/SKILL.md";
3338
- var TRUTHMARK_PREVIEW_SKILL_METADATA_PATH = ".agents/skills/truthmark-preview/agents/openai.yaml";
3339
3451
  var TRUTHMARK_PORTAL_SKILL_PATH = ".agents/skills/truthmark-portal/SKILL.md";
3340
3452
  var TRUTHMARK_PORTAL_SKILL_METADATA_PATH = ".agents/skills/truthmark-portal/agents/openai.yaml";
3341
3453
  var TRUTHMARK_ROUTE_AUDITOR_AGENT_PATH = ".codex/agents/truth-route-auditor.toml";
@@ -3394,12 +3506,10 @@ var workflowSupportFiles = (workflowId) => {
3394
3506
  const workflow = getTruthmarkWorkflow(workflowId);
3395
3507
  const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
3396
3508
  const hasSubagentSupport = definition.parentRule !== void 0 && ((workflow.subagents?.length ?? 0) > 0 || (workflow.writeSubagents?.length ?? 0) > 0);
3397
- const hasHelperSupport = (workflow.helpers?.length ?? 0) > 0;
3398
3509
  return [
3399
3510
  "support/procedure.md",
3400
3511
  "support/report-template.md",
3401
- ...hasSubagentSupport ? ["support/subagents-and-leases.md"] : [],
3402
- ...hasHelperSupport ? ["helper-manifest.yml", "support/helper-policy.md"] : []
3512
+ ...hasSubagentSupport ? ["support/subagents-and-leases.md"] : []
3403
3513
  ];
3404
3514
  };
3405
3515
  var renderWorkflowCommandAdapterInstructions = (workflowId, root, hostName, surfaceKind) => {
@@ -3474,7 +3584,7 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3474
3584
  "truthmark-preview": {
3475
3585
  title: "Truthmark Preview",
3476
3586
  argumentHint: "Optional requested outcome, code area, doc path, or routing question",
3477
- invocations: TRUTH_PREVIEW_EXPLICIT_INVOCATIONS,
3587
+ invocations: TRUTH_PREVIEW_EXPLICIT_INVOCATIONS.copilot,
3478
3588
  use: () => "Use this skill only when the user explicitly asks to preview Truthmark routing or workflow choice before edits.",
3479
3589
  quickRules: (config) => [
3480
3590
  "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
@@ -3522,7 +3632,7 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3522
3632
  use: () => "Use this skill only when the user explicitly asks to generate or refresh the committed static HTML Truthmark Portal.",
3523
3633
  quickRules: (config) => [
3524
3634
  "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
3525
- "Truthmark Portal is manual-only; never run it as a completion gate and never treat it as Truth Sync.",
3635
+ "Truthmark Portal is manual-only; never run it automatically at completion and never treat it as Truth Sync.",
3526
3636
  "Markdown remains canonical; generated HTML is non-canonical presentation only.",
3527
3637
  "Read Markdown directly; the workflow does not require the truthmark CLI or package.",
3528
3638
  "Generate committed, generated non-canonical static files for humans.",
@@ -3556,8 +3666,27 @@ ${renderMarkdownExample(
3556
3666
  ${renderMarkdownExample(
3557
3667
  renderTruthSyncCompletedReport({
3558
3668
  changedCode: ["src/auth/session.ts"],
3669
+ syncIntent: {
3670
+ changedCodeReviewed: ["src/auth/session.ts"],
3671
+ affectedRouteOrTruthOwner: [config.truthmark.paths.routesIndex],
3672
+ targetTruthDocs: [
3673
+ `${engineeringTruthRoot}/repository/bootstrap-routing.md`
3674
+ ],
3675
+ intendedUpdate: ["Update session timeout behavior."],
3676
+ evidenceToVerify: [
3677
+ "src/auth/session.ts:12",
3678
+ `${config.truthmark.paths.routesIndex}:11`
3679
+ ],
3680
+ userProvidedDecisionRationale: [
3681
+ "User rationale: session timeout behavior changed for internal implementation consistency"
3682
+ ],
3683
+ noUpdateNeededRationale: ["not applicable; mapped truth is stale"],
3684
+ blockers: ["none"]
3685
+ },
3559
3686
  ownershipReviewed: [config.truthmark.paths.routesIndex],
3560
- truthDocsUpdated: [`${engineeringTruthRoot}/repository/overview.md`],
3687
+ truthDocsUpdated: [
3688
+ `${engineeringTruthRoot}/repository/bootstrap-routing.md`
3689
+ ],
3561
3690
  evidenceChecked: [
3562
3691
  {
3563
3692
  claim: "Session timeout behavior is documented in the mapped repository truth doc.",
@@ -3568,8 +3697,8 @@ ${renderMarkdownExample(
3568
3697
  result: "supported"
3569
3698
  }
3570
3699
  ],
3571
- helperScripts: [
3572
- "validate-write-lease: skipped, no write lease used"
3700
+ decisionRationaleCaptured: [
3701
+ "Placed user rationale in the mapped engineering truth doc under Engineering Decisions/Rationale."
3573
3702
  ],
3574
3703
  notes: ["Updated session timeout behavior."]
3575
3704
  })
@@ -3643,61 +3772,11 @@ var renderWorkflowSupportParts = (workflowId, config) => {
3643
3772
  var renderSkillSupportFile = (title, body) => {
3644
3773
  return `# ${title}
3645
3774
 
3646
- Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
3775
+ Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
3647
3776
 
3648
3777
  ${body}
3649
3778
  `;
3650
3779
  };
3651
- var renderHelperManifest = (helpers) => {
3652
- const manifest = {
3653
- helpers: Object.fromEntries(
3654
- helpers.map((helper) => [
3655
- helper.id,
3656
- {
3657
- optional: helper.optional,
3658
- runner: helper.runner,
3659
- command: helper.command,
3660
- inputs: helper.inputs,
3661
- output: helper.output,
3662
- writes: helper.writes,
3663
- ...helper.allowedWrites === void 0 ? {} : { allowedWrites: helper.allowedWrites },
3664
- fallback: helper.fallback
3665
- }
3666
- ])
3667
- )
3668
- };
3669
- return [
3670
- `# Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.`,
3671
- stringify2(manifest, { lineWidth: 0 })
3672
- ].join("\n");
3673
- };
3674
- var renderHelperPolicySupport = (helpers) => {
3675
- const reportHelperId = helpers.find((helper) => helper.id.endsWith("-report"))?.id ?? helpers[0]?.id;
3676
- const helperLines = helpers.map(
3677
- (helper) => `- ${helper.id}: optional ${helper.runner}; manual fallback: ${helper.fallback}`
3678
- ).join("\n");
3679
- return renderSkillSupportFile(
3680
- "Optional Helper CLI Policy",
3681
- `Optional helper CLI commands may collect deterministic checkout facts or validate artifacts. If the Truthmark CLI is unavailable or too old for a declared helper, continue manually using this procedure and report which helper was skipped. Helper output is derived evidence; it does not override direct checkout inspection, workflow write boundaries, or parent acceptance.
3682
-
3683
- Runner detection:
3684
- - Check the declared Truthmark CLI runner before invoking a helper.
3685
- - Invoke helpers through the installed \`truthmark validate ... --json\` CLI command using argv-style arguments from helper-manifest.yml.
3686
- - If unavailable or version-mismatched, treat the helper as skipped and use the manual fallback.
3687
- - Do not fail the workflow solely because a helper cannot run.
3688
-
3689
- Available helpers:
3690
- ${helperLines}
3691
-
3692
- Final reports should include helper status when helpers are declared for this workflow:
3693
-
3694
- \`\`\`md
3695
- Helper scripts:
3696
- - ${reportHelperId}: ran, passed
3697
- - validate-write-lease: skipped, no write lease used
3698
- \`\`\``
3699
- );
3700
- };
3701
3780
  var renderWorkflowProcedure = (workflowId, config) => {
3702
3781
  switch (workflowId) {
3703
3782
  case "truthmark-structure":
@@ -3721,7 +3800,7 @@ var renderWorkflowEntrypoint = (workflowId, config, supportFiles, host) => {
3721
3800
  const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
3722
3801
  const supportFileUsage = (supportFile) => {
3723
3802
  if (supportFile === "support/procedure.md") {
3724
- return "read before edits or detailed auditing; contains core quality gates";
3803
+ return "read before edits or detailed auditing; contains core review questions";
3725
3804
  }
3726
3805
  if (supportFile === "support/report-template.md") {
3727
3806
  return "read before the final report";
@@ -3729,12 +3808,6 @@ var renderWorkflowEntrypoint = (workflowId, config, supportFiles, host) => {
3729
3808
  if (supportFile === "support/subagents-and-leases.md") {
3730
3809
  return "read only when using subagents, leases, or accepting worker output";
3731
3810
  }
3732
- if (supportFile === "support/helper-policy.md") {
3733
- return "read only when invoking helper validators or reporting helper status";
3734
- }
3735
- if (supportFile === "helper-manifest.yml") {
3736
- return "read only when invoking helper validators or validating helper registration";
3737
- }
3738
3811
  return "available when relevant to the current step";
3739
3812
  };
3740
3813
  const supportFileList = supportFiles.map((supportFile) => `- ${supportFile} \u2014 ${supportFileUsage(supportFile)}`).join("\n");
@@ -3744,7 +3817,6 @@ name: ${workflowId}
3744
3817
  description: ${workflow.description}
3745
3818
  argument-hint: ${definition.argumentHint}
3746
3819
  user-invocable: true
3747
- truthmark-version: ${TRUTHMARK_VERSION}
3748
3820
  ---
3749
3821
 
3750
3822
  # ${definition.title}
@@ -3819,7 +3891,6 @@ var renderTruthmarkSkillPackage = ({
3819
3891
  config
3820
3892
  );
3821
3893
  const subagents = renderWorkflowSubagentSupport(workflowId, host);
3822
- const helpers = getTruthmarkWorkflow(workflowId).helpers ?? [];
3823
3894
  const supportFiles = workflowSupportFiles(workflowId);
3824
3895
  const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
3825
3896
  const files = [
@@ -3851,18 +3922,6 @@ var renderTruthmarkSkillPackage = ({
3851
3922
  )
3852
3923
  });
3853
3924
  }
3854
- if (helpers.length > 0) {
3855
- files.push(
3856
- {
3857
- path: `${skillDirectory}/helper-manifest.yml`,
3858
- content: renderHelperManifest(helpers)
3859
- },
3860
- {
3861
- path: `${supportDirectory}/helper-policy.md`,
3862
- content: renderHelperPolicySupport(helpers)
3863
- }
3864
- );
3865
- }
3866
3925
  return files;
3867
3926
  };
3868
3927
  var normalizeOpenCodePermissionPath = (path12) => {
@@ -3972,7 +4031,7 @@ var renderCodexReadOnlyAgent = ({
3972
4031
  nicknameCandidates,
3973
4032
  developerInstructions
3974
4033
  }) => {
3975
- return `# Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4034
+ return `# Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
3976
4035
  name = ${renderTomlString(name)}
3977
4036
  description = ${renderTomlString(description)}
3978
4037
  sandbox_mode = "read-only"
@@ -3988,7 +4047,7 @@ var renderCodexWriteAgent = ({
3988
4047
  nicknameCandidates,
3989
4048
  developerInstructions
3990
4049
  }) => {
3991
- return `# Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4050
+ return `# Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
3992
4051
  name = ${renderTomlString(name)}
3993
4052
  description = ${renderTomlString(description)}
3994
4053
  sandbox_mode = "workspace-write"
@@ -4010,7 +4069,7 @@ description: ${description}
4010
4069
  tools: [read, search]
4011
4070
  ---
4012
4071
 
4013
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4072
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4014
4073
 
4015
4074
  ${agentInstructions}
4016
4075
  `;
@@ -4026,7 +4085,7 @@ description: ${description}
4026
4085
  tools: [read, search, edit]
4027
4086
  ---
4028
4087
 
4029
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4088
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4030
4089
 
4031
4090
  ${instructions}
4032
4091
  `;
@@ -4044,7 +4103,7 @@ kind: local
4044
4103
  tools: [read_file, grep_search]
4045
4104
  ---
4046
4105
 
4047
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4106
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4048
4107
 
4049
4108
  Manual invocation: @${copilotName}
4050
4109
 
@@ -4063,7 +4122,7 @@ kind: local
4063
4122
  tools: [read_file, grep_search, write_file]
4064
4123
  ---
4065
4124
 
4066
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4125
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4067
4126
 
4068
4127
  Manual invocation: @${copilotName} with an explicit parent write lease.
4069
4128
 
@@ -4082,7 +4141,7 @@ description: ${description}
4082
4141
  tools: Read, Grep, Glob, LS
4083
4142
  ---
4084
4143
 
4085
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4144
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4086
4145
 
4087
4146
  Manual invocation: use the ${copilotName} subagent.
4088
4147
 
@@ -4100,7 +4159,7 @@ description: ${description}
4100
4159
  tools: Read, Grep, Glob, LS, Edit, MultiEdit
4101
4160
  ---
4102
4161
 
4103
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4162
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4104
4163
 
4105
4164
  Manual invocation: use the ${copilotName} subagent with an explicit parent write lease.
4106
4165
 
@@ -4233,7 +4292,7 @@ permission:
4233
4292
  "grep *": allow
4234
4293
  ---
4235
4294
 
4236
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4295
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4237
4296
 
4238
4297
  Manual invocation: @${invocation}
4239
4298
 
@@ -4268,7 +4327,7 @@ ${editAllowRules}
4268
4327
  "git diff*": allow
4269
4328
  ---
4270
4329
 
4271
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4330
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4272
4331
 
4273
4332
  Manual invocation: @${invocation}
4274
4333
 
@@ -4319,7 +4378,6 @@ policy:
4319
4378
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4320
4379
 
4321
4380
  truthmark:
4322
- version: "${TRUTHMARK_VERSION}"
4323
4381
  refresh_command: "truthmark init"
4324
4382
  `;
4325
4383
  };
@@ -4334,7 +4392,6 @@ policy:
4334
4392
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4335
4393
 
4336
4394
  truthmark:
4337
- version: "${TRUTHMARK_VERSION}"
4338
4395
  refresh_command: "truthmark init"
4339
4396
  `;
4340
4397
  };
@@ -4349,7 +4406,6 @@ policy:
4349
4406
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4350
4407
 
4351
4408
  truthmark:
4352
- version: "${TRUTHMARK_VERSION}"
4353
4409
  refresh_command: "truthmark init"
4354
4410
  `;
4355
4411
  };
@@ -4373,7 +4429,7 @@ Workflow:
4373
4429
  3. ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
4374
4430
  ${renderTruthDocOwnershipGateSection(
4375
4431
  "source truth docs before writing code",
4376
- "if a source truth doc is broad, mixed-owner, index-like, unrouteable, stale, or conflicts with implementation evidence, block before writing code and recommend Truth Structure or Truth Document"
4432
+ "if a source truth doc is broad, mixed-owner, index-like, unrouteable, stale, or conflicts with implementation evidence, stop before writing code and recommend Truth Structure or Truth Document"
4377
4433
  )}
4378
4434
  4. Update functional code only so implementation matches bounded, current truth claims from the source docs.
4379
4435
  5. Do not edit truth docs or truth routing while realizing those docs.
@@ -4398,22 +4454,6 @@ policy:
4398
4454
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4399
4455
 
4400
4456
  truthmark:
4401
- version: "${TRUTHMARK_VERSION}"
4402
- refresh_command: "truthmark init"
4403
- `;
4404
- };
4405
- var renderTruthmarkPreviewSkillMetadata = () => {
4406
- const workflow = getTruthmarkWorkflow("truthmark-preview");
4407
- return `interface:
4408
- display_name: "${workflow.displayName}"
4409
- short_description: "${workflow.shortDescription}"
4410
- default_prompt: "${workflow.defaultPrompt}"
4411
-
4412
- policy:
4413
- allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4414
-
4415
- truthmark:
4416
- version: "${TRUTHMARK_VERSION}"
4417
4457
  refresh_command: "truthmark init"
4418
4458
  `;
4419
4459
  };
@@ -4428,7 +4468,6 @@ policy:
4428
4468
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4429
4469
 
4430
4470
  truthmark:
4431
- version: "${TRUTHMARK_VERSION}"
4432
4471
  refresh_command: "truthmark init"
4433
4472
  `;
4434
4473
  };
@@ -4443,7 +4482,6 @@ policy:
4443
4482
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4444
4483
 
4445
4484
  truthmark:
4446
- version: "${TRUTHMARK_VERSION}"
4447
4485
  refresh_command: "truthmark init"
4448
4486
  `;
4449
4487
  };
@@ -4507,10 +4545,15 @@ var renderTruthmarkGeminiCheckCommand = (config = defaultAgentConfig()) => {
4507
4545
  );
4508
4546
  };
4509
4547
  var renderTruthmarkGeminiPreviewCommand = (config = defaultAgentConfig()) => {
4510
- void config;
4511
- return renderGeminiWorkflowCommand(
4512
- "truthmark-preview",
4513
- ".gemini/skills/truthmark-preview"
4548
+ const workflow = getTruthmarkWorkflow("truthmark-preview");
4549
+ return renderGeminiCommand(
4550
+ workflow.description,
4551
+ `This command is the Gemini CLI entrypoint for Truthmark Preview.
4552
+
4553
+ Truth Preview is read-only and explicit. Do not invoke another Truthmark command from here.
4554
+
4555
+ ${renderTruthPreviewProcedureBody(config, "gemini")}
4556
+ ${renderTruthPreviewAdapterReportFields()}`
4514
4557
  );
4515
4558
  };
4516
4559
  var renderTruthmarkGeminiPortalCommand = (config = defaultAgentConfig()) => {
@@ -4556,10 +4599,15 @@ var renderTruthmarkCopilotCheckPrompt = (config = defaultAgentConfig()) => {
4556
4599
  );
4557
4600
  };
4558
4601
  var renderTruthmarkCopilotPreviewPrompt = (config = defaultAgentConfig()) => {
4559
- void config;
4560
- return renderCopilotWorkflowPrompt(
4561
- "truthmark-preview",
4562
- ".github/skills/truthmark-preview"
4602
+ const workflow = getTruthmarkWorkflow("truthmark-preview");
4603
+ return renderCopilotPromptFile(
4604
+ workflow.description,
4605
+ `This prompt is the GitHub Copilot entrypoint for Truthmark Preview.
4606
+
4607
+ Truth Preview is read-only and explicit. Do not invoke another Truthmark command from here.
4608
+
4609
+ ${renderTruthPreviewProcedureBody(config, "copilot")}
4610
+ ${renderTruthPreviewAdapterReportFields()}`
4563
4611
  );
4564
4612
  };
4565
4613
  var renderTruthmarkCopilotPortalPrompt = (config = defaultAgentConfig()) => {
@@ -4603,16 +4651,6 @@ var codexFiles = (config) => {
4603
4651
  path: TRUTHMARK_SYNC_SKILL_METADATA_PATH,
4604
4652
  content: renderTruthmarkSyncSkillMetadata()
4605
4653
  },
4606
- ...renderTruthmarkSkillPackage({
4607
- skillPath: TRUTHMARK_PREVIEW_SKILL_PATH,
4608
- workflowId: "truthmark-preview",
4609
- host: "codex",
4610
- config
4611
- }),
4612
- {
4613
- path: TRUTHMARK_PREVIEW_SKILL_METADATA_PATH,
4614
- content: renderTruthmarkPreviewSkillMetadata()
4615
- },
4616
4654
  ...renderTruthmarkSkillPackage({
4617
4655
  skillPath: TRUTHMARK_CHECK_SKILL_PATH,
4618
4656
  workflowId: "truthmark-check",
@@ -4686,12 +4724,6 @@ var opencodeFiles = (config) => {
4686
4724
  host: "opencode",
4687
4725
  config
4688
4726
  }),
4689
- ...renderTruthmarkSkillPackage({
4690
- skillPath: ".opencode/skills/truthmark-preview/SKILL.md",
4691
- workflowId: "truthmark-preview",
4692
- host: "opencode",
4693
- config
4694
- }),
4695
4727
  ...renderTruthmarkSkillPackage({
4696
4728
  skillPath: ".opencode/skills/truthmark-check/SKILL.md",
4697
4729
  workflowId: "truthmark-check",
@@ -4754,12 +4786,6 @@ var claudeFiles = (config, block) => {
4754
4786
  host: "claude-code",
4755
4787
  config
4756
4788
  }),
4757
- ...renderTruthmarkSkillPackage({
4758
- skillPath: ".claude/skills/truthmark-preview/SKILL.md",
4759
- workflowId: "truthmark-preview",
4760
- host: "claude-code",
4761
- config
4762
- }),
4763
4789
  ...renderTruthmarkSkillPackage({
4764
4790
  skillPath: ".claude/skills/truthmark-check/SKILL.md",
4765
4791
  workflowId: "truthmark-check",
@@ -4822,12 +4848,6 @@ var copilotFiles = (config, block) => {
4822
4848
  host: "github-copilot",
4823
4849
  config
4824
4850
  }),
4825
- ...renderTruthmarkSkillPackage({
4826
- skillPath: ".github/skills/truthmark-preview/SKILL.md",
4827
- workflowId: "truthmark-preview",
4828
- host: "github-copilot",
4829
- config
4830
- }),
4831
4851
  ...renderTruthmarkSkillPackage({
4832
4852
  skillPath: ".github/skills/truthmark-check/SKILL.md",
4833
4853
  workflowId: "truthmark-check",
@@ -4918,12 +4938,6 @@ var geminiFiles = (config, block) => {
4918
4938
  host: "gemini-cli",
4919
4939
  config
4920
4940
  }),
4921
- ...renderTruthmarkSkillPackage({
4922
- skillPath: ".gemini/skills/truthmark-preview/SKILL.md",
4923
- workflowId: "truthmark-preview",
4924
- host: "gemini-cli",
4925
- config
4926
- }),
4927
4941
  ...renderTruthmarkSkillPackage({
4928
4942
  skillPath: ".gemini/skills/truthmark-check/SKILL.md",
4929
4943
  workflowId: "truthmark-check",
@@ -5026,6 +5040,163 @@ var renderGeneratedSurfaces = (config, block = renderAgentsBlock(config)) => {
5026
5040
  ).sort((left, right) => left.path.localeCompare(right.path));
5027
5041
  };
5028
5042
 
5043
+ // src/checks/generated-surfaces.ts
5044
+ var readOptionalFile = async (rootDir, filePath) => {
5045
+ try {
5046
+ return await fs6.readFile(resolveRepoPath(rootDir, filePath), "utf8");
5047
+ } catch (error) {
5048
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
5049
+ return null;
5050
+ }
5051
+ throw error;
5052
+ }
5053
+ };
5054
+ var extractManagedBlock = (content) => {
5055
+ const startIndex = content.indexOf(TRUTHMARK_BLOCK_START);
5056
+ const endIndex = content.indexOf(TRUTHMARK_BLOCK_END);
5057
+ if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
5058
+ return null;
5059
+ }
5060
+ return content.slice(startIndex, endIndex + TRUTHMARK_BLOCK_END.length);
5061
+ };
5062
+ var normalizeGeneratedSurfaceContent = (content) => {
5063
+ if (content === null) {
5064
+ return null;
5065
+ }
5066
+ return content.replace(/\r\n/g, "\n").replace(/\n$/u, "");
5067
+ };
5068
+ var GENERATED_HOST_SKILL_ROOTS = [
5069
+ ".agents/skills",
5070
+ ".opencode/skills",
5071
+ ".claude/skills",
5072
+ ".github/skills",
5073
+ ".gemini/skills"
5074
+ ];
5075
+ var RETIRED_SKILL_HELPER_PATHS = [
5076
+ "helper-manifest.yml",
5077
+ "support/helper-policy.md"
5078
+ ];
5079
+ var RETIRED_PACKAGE_DIRECTORIES = ["truthmark-preview"];
5080
+ var pathExists = async (absolutePath) => {
5081
+ try {
5082
+ await fs6.access(absolutePath);
5083
+ return true;
5084
+ } catch (error) {
5085
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
5086
+ return false;
5087
+ }
5088
+ throw error;
5089
+ }
5090
+ };
5091
+ var listDirectoryFiles = async (rootDir, packageRoot) => {
5092
+ const stack = [packageRoot];
5093
+ const files = [];
5094
+ while (stack.length > 0) {
5095
+ const current = stack.pop();
5096
+ if (current === void 0) {
5097
+ continue;
5098
+ }
5099
+ const absoluteCurrent = resolveRepoPath(rootDir, current);
5100
+ const entries = await fs6.readdir(absoluteCurrent, {
5101
+ withFileTypes: true
5102
+ });
5103
+ for (const entry of entries) {
5104
+ const next = `${current}/${entry.name}`;
5105
+ if (entry.isDirectory()) {
5106
+ stack.push(next);
5107
+ } else {
5108
+ files.push(next);
5109
+ }
5110
+ }
5111
+ }
5112
+ return files;
5113
+ };
5114
+ var collectRetiredGeneratedSurfaces = async (rootDir, expectedSurfacePaths) => {
5115
+ const legacyCandidates = /* @__PURE__ */ new Set();
5116
+ for (const skillRoot of GENERATED_HOST_SKILL_ROOTS) {
5117
+ const absoluteSkillRoot = resolveRepoPath(rootDir, skillRoot);
5118
+ let skillPackages = [];
5119
+ try {
5120
+ skillPackages = await fs6.readdir(absoluteSkillRoot, {
5121
+ withFileTypes: true
5122
+ });
5123
+ } catch (error) {
5124
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
5125
+ continue;
5126
+ }
5127
+ throw error;
5128
+ }
5129
+ const packageNames = skillPackages.filter((entry) => entry.isDirectory() && entry.name.startsWith("truthmark-")).map((entry) => entry.name);
5130
+ for (const packageName of RETIRED_PACKAGE_DIRECTORIES) {
5131
+ if (!packageNames.includes(packageName)) {
5132
+ continue;
5133
+ }
5134
+ const packageRoot = `${skillRoot}/${packageName}`;
5135
+ const packageFiles = await listDirectoryFiles(rootDir, packageRoot);
5136
+ for (const filePath of packageFiles) {
5137
+ if (!expectedSurfacePaths.has(filePath)) {
5138
+ legacyCandidates.add(filePath);
5139
+ }
5140
+ }
5141
+ }
5142
+ for (const packageName of packageNames) {
5143
+ for (const retiredName of RETIRED_SKILL_HELPER_PATHS) {
5144
+ const retiredPath = `${packageName}/${retiredName}`;
5145
+ const relativeRetiredPath = `${skillRoot}/${retiredPath}`;
5146
+ const absoluteRetiredPath = resolveRepoPath(rootDir, relativeRetiredPath);
5147
+ if (await pathExists(absoluteRetiredPath) && !expectedSurfacePaths.has(relativeRetiredPath)) {
5148
+ legacyCandidates.add(relativeRetiredPath);
5149
+ }
5150
+ }
5151
+ }
5152
+ }
5153
+ return Array.from(legacyCandidates);
5154
+ };
5155
+ var checkGeneratedSurfaces = async (rootDir, config) => {
5156
+ const diagnostics = [];
5157
+ const renderedSurfaces = renderGeneratedSurfaces(config);
5158
+ for (const surface of renderedSurfaces) {
5159
+ const content = await readOptionalFile(rootDir, surface.path);
5160
+ if (content === null) {
5161
+ diagnostics.push({
5162
+ category: "generated-surface",
5163
+ severity: "review",
5164
+ message: `Generated surface ${surface.path} is missing; rerun truthmark init.`,
5165
+ file: surface.path
5166
+ });
5167
+ continue;
5168
+ }
5169
+ const comparableContent = normalizeGeneratedSurfaceContent(
5170
+ surface.managedBlock ? extractManagedBlock(content) : content
5171
+ );
5172
+ const expectedContent = normalizeGeneratedSurfaceContent(surface.content);
5173
+ if (comparableContent !== expectedContent) {
5174
+ diagnostics.push({
5175
+ category: "generated-surface",
5176
+ severity: "review",
5177
+ message: `Generated surface ${surface.path} is stale; rerun truthmark init.`,
5178
+ file: surface.path
5179
+ });
5180
+ }
5181
+ }
5182
+ const staleSurfaces = await collectRetiredGeneratedSurfaces(
5183
+ rootDir,
5184
+ new Set(renderedSurfaces.map((surface) => surface.path))
5185
+ );
5186
+ for (const surfacePath of staleSurfaces) {
5187
+ diagnostics.push({
5188
+ category: "generated-surface",
5189
+ severity: "review",
5190
+ message: `Generated surface ${surfacePath} is obsolete; rerun truthmark init.`,
5191
+ file: surfacePath
5192
+ });
5193
+ }
5194
+ return diagnostics;
5195
+ };
5196
+ var findRetiredGeneratedSurfaces = async (rootDir, expectedSurfacePaths) => {
5197
+ return collectRetiredGeneratedSurfaces(rootDir, expectedSurfacePaths);
5198
+ };
5199
+
5029
5200
  // src/init/init.ts
5030
5201
  var escapeRegExp = (value) => {
5031
5202
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -5201,9 +5372,17 @@ var runInit = async (cwd) => {
5201
5372
  results.push(...await scaffoldHierarchy(rootDir, config));
5202
5373
  const block = renderAgentsBlock(config);
5203
5374
  const platformFiles = renderGeneratedSurfaces(config, block);
5375
+ const expectedSurfacePaths = new Set(platformFiles.map((file) => file.path));
5204
5376
  for (const file of platformFiles) {
5205
5377
  results.push(await writePlatformFile(rootDir, file));
5206
5378
  }
5379
+ const obsoleteSurfacePaths = await findRetiredGeneratedSurfaces(
5380
+ rootDir,
5381
+ expectedSurfacePaths
5382
+ );
5383
+ for (const obsoletePath of obsoleteSurfacePaths) {
5384
+ await fs7.rm(resolveRepoPath(rootDir, obsoletePath), { force: true });
5385
+ }
5207
5386
  const changedResults = results.filter((result) => result.status !== "unchanged");
5208
5387
  return {
5209
5388
  command: "init",
@@ -5298,7 +5477,7 @@ import fg2 from "fast-glob";
5298
5477
  var looksLikeGlob = (pattern) => {
5299
5478
  return /[*?[\]{}()!+@]/u.test(pattern);
5300
5479
  };
5301
- var pathExists = async (absolutePath) => {
5480
+ var pathExists2 = async (absolutePath) => {
5302
5481
  try {
5303
5482
  await fs9.stat(absolutePath);
5304
5483
  return true;
@@ -5368,7 +5547,7 @@ var checkControlledPaths = async (rootDir, controlledPaths) => {
5368
5547
  });
5369
5548
  continue;
5370
5549
  }
5371
- if (!await pathExists(absoluteEntryPath)) {
5550
+ if (!await pathExists2(absoluteEntryPath)) {
5372
5551
  diagnostics.push({
5373
5552
  category: "authority",
5374
5553
  severity: "error",
@@ -5586,7 +5765,7 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
5586
5765
  // src/checks/links.ts
5587
5766
  import fs11 from "fs/promises";
5588
5767
  import path5 from "path";
5589
- var pathExists2 = async (absolutePath) => {
5768
+ var pathExists3 = async (absolutePath) => {
5590
5769
  try {
5591
5770
  await fs11.stat(absolutePath);
5592
5771
  return true;
@@ -5632,7 +5811,7 @@ var checkLinks = async (rootDir, markdownPaths) => {
5632
5811
  });
5633
5812
  continue;
5634
5813
  }
5635
- if (!await pathExists2(absoluteTarget)) {
5814
+ if (!await pathExists3(absoluteTarget)) {
5636
5815
  diagnostics.push({
5637
5816
  category: "links",
5638
5817
  severity: "error",
@@ -6008,7 +6187,7 @@ var classifyPath = (filePath, ignorePatterns) => {
6008
6187
  var looksLikeGlob2 = (pattern) => {
6009
6188
  return /[*?[\]{}()!+@]/u.test(pattern);
6010
6189
  };
6011
- var pathExists3 = async (absolutePath) => {
6190
+ var pathExists4 = async (absolutePath) => {
6012
6191
  try {
6013
6192
  await fs13.stat(absolutePath);
6014
6193
  return true;
@@ -6300,7 +6479,7 @@ var checkAreas = async (rootDir, config) => {
6300
6479
  areaHasTruthDocumentErrors = true;
6301
6480
  continue;
6302
6481
  }
6303
- if (!await pathExists3(absoluteTruthDocumentPath)) {
6482
+ if (!await pathExists4(absoluteTruthDocumentPath)) {
6304
6483
  diagnostics.push({
6305
6484
  category: "area-index",
6306
6485
  severity: "error",
@@ -6404,7 +6583,7 @@ var checkAreas = async (rootDir, config) => {
6404
6583
  entry.valid = false;
6405
6584
  continue;
6406
6585
  }
6407
- if (!await pathExists3(absoluteCodeSurfacePath)) {
6586
+ if (!await pathExists4(absoluteCodeSurfacePath)) {
6408
6587
  diagnostics.push({
6409
6588
  category: "area-index",
6410
6589
  severity: "error",
@@ -6608,95 +6787,12 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
6608
6787
  return diagnostics;
6609
6788
  };
6610
6789
 
6611
- // src/checks/generated-surfaces.ts
6612
- import fs15 from "fs/promises";
6613
- var readOptionalFile = async (rootDir, filePath) => {
6614
- try {
6615
- return await fs15.readFile(resolveRepoPath(rootDir, filePath), "utf8");
6616
- } catch (error) {
6617
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
6618
- return null;
6619
- }
6620
- throw error;
6621
- }
6622
- };
6623
- var extractManagedBlock = (content) => {
6624
- const startIndex = content.indexOf(TRUTHMARK_BLOCK_START);
6625
- const endIndex = content.indexOf(TRUTHMARK_BLOCK_END);
6626
- if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
6627
- return null;
6628
- }
6629
- return content.slice(startIndex, endIndex + TRUTHMARK_BLOCK_END.length);
6630
- };
6631
- var normalizeGeneratedSurfaceContent = (content) => {
6632
- if (content === null) {
6633
- return null;
6634
- }
6635
- return content.replace(/\r\n/g, "\n").replace(/\n$/u, "");
6636
- };
6637
- var versionMarkers = (content) => {
6638
- const markers = [];
6639
- const patterns = [
6640
- /truthmark-version:\s*([^\s]+)/gu,
6641
- /Generated by Truthmark\s+([^\s.]+(?:\.[^\s.]+){1,2})/gu,
6642
- /^version:\s*"(\d+\.\d+\.\d+)"\s*$/gmu
6643
- ];
6644
- for (const pattern of patterns) {
6645
- for (const match of content.matchAll(pattern)) {
6646
- if (match[1]) {
6647
- markers.push(match[1]);
6648
- }
6649
- }
6650
- }
6651
- return markers;
6652
- };
6653
- var checkGeneratedSurfaces = async (rootDir, config) => {
6654
- const diagnostics = [];
6655
- for (const surface of renderGeneratedSurfaces(config)) {
6656
- const content = await readOptionalFile(rootDir, surface.path);
6657
- if (content === null) {
6658
- diagnostics.push({
6659
- category: "generated-surface",
6660
- severity: "review",
6661
- message: `Generated surface ${surface.path} is missing; rerun truthmark init.`,
6662
- file: surface.path
6663
- });
6664
- continue;
6665
- }
6666
- const comparableContent = normalizeGeneratedSurfaceContent(
6667
- surface.managedBlock ? extractManagedBlock(content) : content
6668
- );
6669
- const expectedContent = normalizeGeneratedSurfaceContent(surface.content);
6670
- if (comparableContent !== expectedContent) {
6671
- diagnostics.push({
6672
- category: "generated-surface",
6673
- severity: "review",
6674
- message: `Generated surface ${surface.path} is stale; rerun truthmark init.`,
6675
- file: surface.path
6676
- });
6677
- }
6678
- const versionContent = surface.managedBlock ? comparableContent ?? "" : content;
6679
- const mismatchedVersions = versionMarkers(versionContent).filter(
6680
- (version) => version !== TRUTHMARK_VERSION
6681
- );
6682
- if (mismatchedVersions.length > 0) {
6683
- diagnostics.push({
6684
- category: "generated-surface",
6685
- severity: "review",
6686
- message: `Generated surface ${surface.path} has Truthmark version ${mismatchedVersions[0]} but current version is ${TRUTHMARK_VERSION}; rerun truthmark init.`,
6687
- file: surface.path
6688
- });
6689
- }
6690
- }
6691
- return diagnostics;
6692
- };
6693
-
6694
6790
  // src/impact/build.ts
6695
6791
  import path10 from "path";
6696
6792
  import micromatch7 from "micromatch";
6697
6793
 
6698
6794
  // src/repo-index/file-tree.ts
6699
- import fs16 from "fs/promises";
6795
+ import fs15 from "fs/promises";
6700
6796
  import path7 from "path";
6701
6797
  import { execa as execa2 } from "execa";
6702
6798
  import fg5 from "fast-glob";
@@ -6872,7 +6968,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6872
6968
  for (const filePath of discoveredFiles.filter((entry) => !isIgnoredPath(entry, ignore)).sort()) {
6873
6969
  let stat;
6874
6970
  try {
6875
- stat = await fs16.stat(path7.join(rootDir, filePath));
6971
+ stat = await fs15.stat(path7.join(rootDir, filePath));
6876
6972
  } catch (error) {
6877
6973
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
6878
6974
  continue;
@@ -6896,7 +6992,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6896
6992
  });
6897
6993
  }
6898
6994
  if (kind === "doc") {
6899
- const source = await fs16.readFile(path7.join(rootDir, filePath), "utf8");
6995
+ const source = await fs15.readFile(path7.join(rootDir, filePath), "utf8");
6900
6996
  const parsed = parseFrontmatter(source);
6901
6997
  const markdown = parseMarkdownDocument(parsed.content);
6902
6998
  const title = markdown.headings.find((heading) => heading.depth === 1)?.text ?? null;
@@ -6924,7 +7020,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6924
7020
  };
6925
7021
 
6926
7022
  // src/repo-index/package-metadata.ts
6927
- import fs17 from "fs/promises";
7023
+ import fs16 from "fs/promises";
6928
7024
  import path8 from "path";
6929
7025
  import fg6 from "fast-glob";
6930
7026
  var packageManagerFor = async (rootDir, packageDir) => {
@@ -6937,7 +7033,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
6937
7033
  ];
6938
7034
  for (const [lockfile, manager] of lockfiles) {
6939
7035
  try {
6940
- await fs17.access(path8.join(rootDir, packageDir, lockfile));
7036
+ await fs16.access(path8.join(rootDir, packageDir, lockfile));
6941
7037
  return manager;
6942
7038
  } catch {
6943
7039
  continue;
@@ -6955,7 +7051,7 @@ var discoverPackageMetadata = async (rootDir) => {
6955
7051
  const packages = [];
6956
7052
  for (const packageFile of packageFiles.sort()) {
6957
7053
  const packageDir = path8.posix.dirname(packageFile) === "." ? "" : path8.posix.dirname(packageFile);
6958
- const raw = JSON.parse(await fs17.readFile(path8.join(rootDir, packageFile), "utf8"));
7054
+ const raw = JSON.parse(await fs16.readFile(path8.join(rootDir, packageFile), "utf8"));
6959
7055
  const scripts = raw.scripts && typeof raw.scripts === "object" ? Object.keys(raw.scripts).sort() : [];
6960
7056
  packages.push({
6961
7057
  path: packageFile,
@@ -7060,7 +7156,7 @@ var buildRepoIndex = async (cwd) => {
7060
7156
  import { execa as execa4 } from "execa";
7061
7157
 
7062
7158
  // src/git/changes.ts
7063
- import fs18 from "fs/promises";
7159
+ import fs17 from "fs/promises";
7064
7160
  import path9 from "path";
7065
7161
  import { execa as execa3 } from "execa";
7066
7162
  var normalizePath3 = (filePath) => {
@@ -7070,9 +7166,9 @@ var listChangedPaths = async (cwd, args) => {
7070
7166
  const result = await execa3("git", args, { cwd });
7071
7167
  return result.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => normalizePath3(line));
7072
7168
  };
7073
- var pathExists4 = async (filePath) => {
7169
+ var pathExists5 = async (filePath) => {
7074
7170
  try {
7075
- await fs18.access(filePath);
7171
+ await fs17.access(filePath);
7076
7172
  return true;
7077
7173
  } catch {
7078
7174
  return false;
@@ -7116,7 +7212,7 @@ var getUncommittedChanges = async (cwd) => {
7116
7212
  const deletedPathCandidates = /* @__PURE__ */ new Set([...stagedDeletedPaths, ...unstagedDeletedPaths]);
7117
7213
  for (const deletedPath of deletedPathCandidates) {
7118
7214
  const change = getOrCreateChange(changesByPath, deletedPath);
7119
- change.deleted = !await pathExists4(path9.join(rootDir, deletedPath));
7215
+ change.deleted = !await pathExists5(path9.join(rootDir, deletedPath));
7120
7216
  }
7121
7217
  return Array.from(changesByPath.values()).sort((left, right) => {
7122
7218
  return left.path.localeCompare(right.path);
@@ -7319,11 +7415,11 @@ var checkFreshness = async (rootDir, _config, _truthDocumentPaths, base) => {
7319
7415
  };
7320
7416
 
7321
7417
  // src/evidence/validate.ts
7322
- import fs20 from "fs/promises";
7418
+ import fs19 from "fs/promises";
7323
7419
  import fg7 from "fast-glob";
7324
7420
 
7325
7421
  // src/evidence/parse.ts
7326
- import fs19 from "fs/promises";
7422
+ import fs18 from "fs/promises";
7327
7423
  import path11 from "path";
7328
7424
  import { parse as parse4 } from "yaml";
7329
7425
  var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
@@ -7343,7 +7439,7 @@ var toEvidenceReference = (truthDocPath, raw) => {
7343
7439
  };
7344
7440
  };
7345
7441
  var parseEvidenceReferences = async (rootDir, truthDocPath) => {
7346
- const source = await fs19.readFile(path11.join(rootDir, truthDocPath), "utf8");
7442
+ const source = await fs18.readFile(path11.join(rootDir, truthDocPath), "utf8");
7347
7443
  const parsed = parseFrontmatter(source);
7348
7444
  const references = [];
7349
7445
  for (const entry of parseSourceReferences(source, truthDocPath)) {
@@ -7374,9 +7470,9 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
7374
7470
  };
7375
7471
 
7376
7472
  // src/evidence/validate.ts
7377
- var pathExists5 = async (filePath) => {
7473
+ var pathExists6 = async (filePath) => {
7378
7474
  try {
7379
- await fs20.access(filePath);
7475
+ await fs19.access(filePath);
7380
7476
  return true;
7381
7477
  } catch {
7382
7478
  return false;
@@ -7421,7 +7517,7 @@ var validateHash = async (rootDir, reference) => {
7421
7517
  if (!reference.contentHash.startsWith("sha256:")) {
7422
7518
  return diagnosticFor(reference, `Evidence hash for ${reference.path} must use sha256:.`);
7423
7519
  }
7424
- const source = await fs20.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7520
+ const source = await fs19.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7425
7521
  const lines = source.split("\n");
7426
7522
  const startLine = reference.startLine ?? 1;
7427
7523
  const endLine = reference.endLine ?? lines.length;
@@ -7435,7 +7531,7 @@ var validateLineSpan = async (rootDir, reference) => {
7435
7531
  if (reference.startLine === void 0 && reference.endLine === void 0) {
7436
7532
  return null;
7437
7533
  }
7438
- const source = await fs20.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7534
+ const source = await fs19.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7439
7535
  const lines = source.split("\n");
7440
7536
  const startLine = reference.startLine ?? 1;
7441
7537
  const endLine = reference.endLine ?? lines.length;
@@ -7450,7 +7546,7 @@ var validateReference = async (rootDir, reference) => {
7450
7546
  }
7451
7547
  const absolutePath = resolveRepoPath(rootDir, reference.path);
7452
7548
  await assertRepoContainment(rootDir, absolutePath);
7453
- if (!await pathExists5(absolutePath)) {
7549
+ if (!await pathExists6(absolutePath)) {
7454
7550
  diagnostics.push(diagnosticFor(reference, `Referenced file ${reference.path} does not exist.`));
7455
7551
  return diagnostics;
7456
7552
  }
@@ -7721,25 +7817,49 @@ var helperCommandsFor = (manifestEntry) => (manifestEntry.helpers ?? []).map((he
7721
7817
  argv: [...helper.command.argv],
7722
7818
  optional: helper.optional
7723
7819
  }));
7724
- var evidenceFor = (manifestEntry) => manifestEntry.requiredGates.filter((gate) => /evidence|ownership|containment/iu.test(gate));
7725
- var baseContext = (manifestEntry, mode, allowedWritePaths, forbiddenWritePaths, writeLeaseRequired) => ({
7820
+ var evidenceFor = (manifestEntry) => manifestEntry.reviewQuestions.filter(
7821
+ (question) => /evidence|ownership|containment/iu.test(question)
7822
+ );
7823
+ var baseContext = (manifestEntry, mode, allowedWritePaths, routeFiles, primaryTruthDocs, candidateStaleTruthDocs, forbiddenWritePaths, writeLeaseRequired) => ({
7726
7824
  mode,
7727
7825
  allowedWritePaths: uniqueSorted3(allowedWritePaths),
7826
+ routeFiles: uniqueSorted3(routeFiles),
7827
+ primaryTruthDocs: uniqueSorted3(primaryTruthDocs),
7828
+ candidateStaleTruthDocs: uniqueSorted3(candidateStaleTruthDocs),
7728
7829
  forbiddenWritePaths: uniqueSorted3(forbiddenWritePaths),
7729
- stopConditions: [...manifestEntry.negativeTriggers, ...manifestEntry.forbiddenAdjacency],
7730
- requiredEvidence: evidenceFor(manifestEntry),
7830
+ stopConditions: [
7831
+ ...manifestEntry.negativeTriggers,
7832
+ ...manifestEntry.forbiddenAdjacency
7833
+ ],
7834
+ evidencePrompts: uniqueSorted3([
7835
+ ...evidenceFor(manifestEntry),
7836
+ ...candidateStaleTruthDocs.length > 0 ? [
7837
+ "Record checkout evidence and the reason before touching a candidate stale truth doc outside primaryTruthDocs."
7838
+ ] : []
7839
+ ]),
7731
7840
  helperValidationCommands: helperCommandsFor(manifestEntry),
7732
7841
  writeLeaseRequired
7733
7842
  });
7734
7843
  var buildWorkflowActionContext = (manifestEntry, data = {}) => {
7735
7844
  if (manifestEntry.id === "truthmark-preview" || manifestEntry.id === "truthmark-check") {
7736
- return baseContext(manifestEntry, "read-only", [], [], false);
7845
+ return baseContext(manifestEntry, "read-only", [], [], [], [], [], false);
7737
7846
  }
7738
7847
  if (manifestEntry.id === "truthmark-sync" || manifestEntry.id === "truthmark-document") {
7848
+ const routeFiles = data.routeFiles ?? [];
7849
+ const primaryTruthDocs = data.primaryTruthDocs ?? data.truthDocs ?? [];
7850
+ const candidateStaleTruthDocs = data.candidateStaleTruthDocs ?? [];
7739
7851
  return baseContext(
7740
7852
  manifestEntry,
7741
7853
  "truth-doc-write",
7742
- [...data.routeIndexPath ? [data.routeIndexPath] : [], ...data.routeFiles ?? [], ...data.truthDocs ?? []],
7854
+ [
7855
+ ...data.routeIndexPath ? [data.routeIndexPath] : [],
7856
+ ...routeFiles,
7857
+ ...primaryTruthDocs,
7858
+ ...candidateStaleTruthDocs
7859
+ ],
7860
+ routeFiles,
7861
+ primaryTruthDocs,
7862
+ candidateStaleTruthDocs,
7743
7863
  [],
7744
7864
  true
7745
7865
  );
@@ -7753,6 +7873,9 @@ var buildWorkflowActionContext = (manifestEntry, data = {}) => {
7753
7873
  ...data.routeFiles ?? [],
7754
7874
  ...data.starterTruthDocs ?? []
7755
7875
  ],
7876
+ data.routeFiles ?? [],
7877
+ data.starterTruthDocs ?? [],
7878
+ [],
7756
7879
  [],
7757
7880
  true
7758
7881
  );
@@ -7762,6 +7885,9 @@ var buildWorkflowActionContext = (manifestEntry, data = {}) => {
7762
7885
  manifestEntry,
7763
7886
  "code-write",
7764
7887
  data.codeWritePaths ?? [],
7888
+ data.routeFiles ?? [],
7889
+ data.truthDocs ?? [],
7890
+ [],
7765
7891
  [
7766
7892
  ...data.routeIndexPath ? [data.routeIndexPath] : [],
7767
7893
  ...data.routeFiles ?? [],
@@ -7776,6 +7902,9 @@ var buildWorkflowActionContext = (manifestEntry, data = {}) => {
7776
7902
  "portal-write",
7777
7903
  data.portalEnabled && data.portalOutputPath ? [`${data.portalOutputPath}/**`] : [],
7778
7904
  [],
7905
+ [],
7906
+ [],
7907
+ [],
7779
7908
  false
7780
7909
  );
7781
7910
  };
@@ -7789,16 +7918,26 @@ var helperCommandsFor2 = (workflow) => (TRUTHMARK_WORKFLOW_MANIFEST[workflow].he
7789
7918
  }));
7790
7919
  var uniqueSorted4 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
7791
7920
  var isWriteCapable = (workflow) => !["truthmark-preview", "truthmark-check"].includes(workflow);
7792
- var DEFAULT_BASE_CANDIDATES = ["@{upstream}", "origin/main", "main", "origin/master", "master"];
7921
+ var DEFAULT_BASE_CANDIDATES = [
7922
+ "@{upstream}",
7923
+ "origin/main",
7924
+ "main",
7925
+ "origin/master",
7926
+ "master"
7927
+ ];
7793
7928
  var selectComparisonBase = async (rootDir, suppliedBase) => {
7794
7929
  if (suppliedBase) {
7795
7930
  return suppliedBase;
7796
7931
  }
7797
7932
  for (const candidate of DEFAULT_BASE_CANDIDATES) {
7798
- const result = await execa5("git", ["rev-parse", "--verify", `${candidate}^{commit}`], {
7799
- cwd: rootDir,
7800
- reject: false
7801
- });
7933
+ const result = await execa5(
7934
+ "git",
7935
+ ["rev-parse", "--verify", `${candidate}^{commit}`],
7936
+ {
7937
+ cwd: rootDir,
7938
+ reject: false
7939
+ }
7940
+ );
7802
7941
  if ((result.exitCode ?? 1) === 0) {
7803
7942
  return candidate;
7804
7943
  }
@@ -7806,59 +7945,110 @@ var selectComparisonBase = async (rootDir, suppliedBase) => {
7806
7945
  return null;
7807
7946
  };
7808
7947
  var routeFilesFor = (repoIndex) => uniqueSorted4(repoIndex.routeMap.routes.map((route) => route.sourcePath));
7948
+ var candidateStaleTruthDocsFor = (indexedTruthDocs, primaryTruthDocs) => {
7949
+ const primary = new Set(primaryTruthDocs);
7950
+ return indexedTruthDocs.filter((truthDoc) => !primary.has(truthDoc));
7951
+ };
7809
7952
  var hasUnmappedFunctionalChange = (impactSet) => impactSet?.diagnostics.some(
7810
- (diagnostic) => diagnostic.category === "impact" && /not mapped to a Truthmark route|no affected truth document/u.test(diagnostic.message)
7953
+ (diagnostic) => diagnostic.category === "impact" && /not mapped to a Truthmark route|no affected truth document/u.test(
7954
+ diagnostic.message
7955
+ )
7811
7956
  ) ?? false;
7812
7957
  var applicabilityFor = (workflow, diagnostics, impactSet) => {
7813
7958
  const reasons = [];
7814
- if (diagnostics.some((diagnostic) => diagnostic.message.includes("Missing .truthmark/config.yml"))) {
7959
+ if (diagnostics.some(
7960
+ (diagnostic) => diagnostic.message.includes("Missing .truthmark/config.yml")
7961
+ )) {
7815
7962
  reasons.push("Missing .truthmark/config.yml.");
7816
- return { state: isWriteCapable(workflow) ? "blocked" : "not_applicable", reasons };
7963
+ return {
7964
+ state: isWriteCapable(workflow) ? "needs_manual_review" : "not_applicable",
7965
+ reasons
7966
+ };
7817
7967
  }
7818
7968
  if (workflow === "truthmark-sync" && !impactSet) {
7819
- reasons.push("truthmark-sync requires --base to derive changed-file impact before exposing sync write paths.");
7820
- return { state: "blocked", reasons };
7969
+ reasons.push(
7970
+ "Choose a comparison base with --base <ref> when workflow status cannot infer one automatically."
7971
+ );
7972
+ return { state: "needs_manual_review", reasons };
7821
7973
  }
7822
7974
  if (workflow === "truthmark-realize" && !impactSet) {
7823
- reasons.push("truthmark-realize requires --base to derive bounded allowed write paths.");
7824
- return { state: "blocked", reasons };
7975
+ reasons.push(
7976
+ "Choose a comparison base with --base <ref> so the helper can suggest bounded code-write paths."
7977
+ );
7978
+ return { state: "needs_manual_review", reasons };
7825
7979
  }
7826
7980
  if (hasUnmappedFunctionalChange(impactSet)) {
7827
- reasons.push("Changed functional files have ambiguous or missing Truthmark route ownership.");
7828
- return { state: "ambiguous", reasons };
7981
+ reasons.push(
7982
+ "Changed functional files need route ownership review before truth-doc suggestions are reliable."
7983
+ );
7984
+ return { state: "needs_routing_review", reasons };
7829
7985
  }
7830
7986
  if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
7831
- reasons.push("Existing diagnostics contain errors that block safe workflow execution.");
7832
- return { state: "blocked", reasons };
7987
+ reasons.push(
7988
+ "Existing diagnostics need manual review before using helper write suggestions."
7989
+ );
7990
+ return { state: "needs_manual_review", reasons };
7833
7991
  }
7834
- return { state: "applicable", reasons };
7992
+ return { state: "ready", reasons };
7993
+ };
7994
+ var workflowCardFor = (workflow, applicability, diagnostics, impactSet) => {
7995
+ const helpers = helperCommandsFor2(workflow);
7996
+ const diagnosticQuestions = diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
7997
+ return {
7998
+ affectedFiles: uniqueSorted4(
7999
+ impactSet?.changedFiles.map((file) => file.path) ?? []
8000
+ ),
8001
+ likelyRouteOwners: uniqueSorted4(
8002
+ impactSet?.affectedRoutes.map((route) => route.sourcePath) ?? []
8003
+ ),
8004
+ suggestedTruthDocs: applicability.state === "needs_routing_review" ? [] : uniqueSorted4(impactSet?.affectedTruthDocs ?? []),
8005
+ openQuestions: uniqueSorted4([
8006
+ ...applicability.reasons,
8007
+ ...diagnosticQuestions
8008
+ ]),
8009
+ skippedHelperStatus: helpers.map((helper) => ({
8010
+ helper: helper.id,
8011
+ status: "skipped",
8012
+ reason: "workflow status does not run optional helpers; inspect the checkout directly or run the helper manually when useful."
8013
+ }))
8014
+ };
7835
8015
  };
7836
8016
  var contextDataFor = (workflow, repoIndex, config, impactSet) => {
7837
8017
  if (!config) {
7838
8018
  return {};
7839
8019
  }
7840
8020
  const routeFiles = routeFilesFor(repoIndex);
7841
- const indexedTruthDocs = uniqueSorted4(repoIndex.routeMap.routes.flatMap((route) => route.truthDocs));
7842
- const truthDocs = workflow === "truthmark-sync" ? indexedTruthDocs : uniqueSorted4(impactSet?.affectedTruthDocs ?? indexedTruthDocs);
8021
+ const indexedTruthDocs = uniqueSorted4(
8022
+ repoIndex.routeMap.routes.flatMap((route) => route.truthDocs)
8023
+ );
8024
+ const primaryTruthDocs = uniqueSorted4(impactSet?.affectedTruthDocs ?? []);
8025
+ const candidateStaleTruthDocs = workflow === "truthmark-sync" ? candidateStaleTruthDocsFor(indexedTruthDocs, primaryTruthDocs) : [];
8026
+ const truthDocs = workflow === "truthmark-sync" ? uniqueSorted4([...primaryTruthDocs, ...candidateStaleTruthDocs]) : uniqueSorted4(impactSet?.affectedTruthDocs ?? indexedTruthDocs);
7843
8027
  return {
7844
8028
  routeIndexPath: config.truthmark.paths.routesIndex,
7845
8029
  routeFiles,
7846
8030
  truthRoot: config.truthmark.paths.truthRoot,
7847
8031
  truthDocs,
8032
+ primaryTruthDocs: workflow === "truthmark-sync" ? primaryTruthDocs : truthDocs,
8033
+ candidateStaleTruthDocs,
7848
8034
  starterTruthDocs: workflow === "truthmark-structure" ? truthDocs : [],
7849
- codeWritePaths: workflow === "truthmark-realize" ? uniqueSorted4(impactSet?.affectedRoutes.flatMap((route) => route.codeSurface) ?? []) : [],
8035
+ codeWritePaths: workflow === "truthmark-realize" ? uniqueSorted4(
8036
+ impactSet?.affectedRoutes.flatMap((route) => route.codeSurface) ?? []
8037
+ ) : [],
7850
8038
  portalEnabled: config.truthmark.generated.portal.enabled,
7851
8039
  portalOutputPath: config.truthmark.paths.portalOutput,
7852
8040
  routes: repoIndex.routeMap.routes
7853
8041
  };
7854
8042
  };
7855
8043
  var nextStepsFor = (workflow, applicability, comparisonBase) => {
7856
- if (applicability.state === "ambiguous") {
7857
- return ["Run Truth Structure or repair route ownership before writing truth docs."];
8044
+ if (applicability.state === "needs_routing_review") {
8045
+ return [
8046
+ "Run Truth Structure or repair route ownership before writing truth docs."
8047
+ ];
7858
8048
  }
7859
- if ((workflow === "truthmark-sync" || workflow === "truthmark-realize") && applicability.state === "blocked" && !comparisonBase) {
8049
+ if ((workflow === "truthmark-sync" || workflow === "truthmark-realize") && applicability.state === "needs_manual_review" && !comparisonBase) {
7860
8050
  return [
7861
- workflow === "truthmark-sync" ? "Rerun with --base <ref> so Truthmark can derive changed-file impact before exposing sync write paths." : "Rerun with --base <ref> so Truthmark can derive bounded allowed code-write paths."
8051
+ workflow === "truthmark-sync" ? "Rerun with --base <ref> so the helper can suggest changed-file impact." : "Rerun with --base <ref> so the helper can suggest bounded code-write paths."
7862
8052
  ];
7863
8053
  }
7864
8054
  return [];
@@ -7873,26 +8063,44 @@ var buildWorkflowState = async (cwd, options) => {
7873
8063
  const loadResult = await loadConfig(rootDir);
7874
8064
  const comparisonBase = options.base ? options.base : options.workflow === "truthmark-sync" ? await selectComparisonBase(rootDir) : null;
7875
8065
  const impactSet = comparisonBase ? await buildImpactSet(rootDir, { base: comparisonBase }) : null;
7876
- const checkResult = await runCheck(cwd, comparisonBase ? { base: comparisonBase } : {});
8066
+ const checkResult = await runCheck(
8067
+ cwd,
8068
+ comparisonBase ? { base: comparisonBase } : {}
8069
+ );
7877
8070
  const diagnostics = [
7878
8071
  ...loadResult.diagnostics,
7879
8072
  ...repoIndex.diagnostics,
7880
8073
  ...impactSet?.diagnostics ?? [],
7881
8074
  ...checkResult.diagnostics
7882
8075
  ];
7883
- const applicability = applicabilityFor(options.workflow, diagnostics, impactSet);
7884
- const actionData = applicability.state === "blocked" || applicability.state === "ambiguous" ? {} : contextDataFor(options.workflow, repoIndex, loadResult.config, impactSet);
8076
+ const applicability = applicabilityFor(
8077
+ options.workflow,
8078
+ diagnostics,
8079
+ impactSet
8080
+ );
8081
+ const actionData = applicability.state === "needs_manual_review" || applicability.state === "needs_routing_review" ? {} : contextDataFor(
8082
+ options.workflow,
8083
+ repoIndex,
8084
+ loadResult.config,
8085
+ impactSet
8086
+ );
7885
8087
  return {
7886
8088
  schemaVersion: "truthmark-workflow/v0",
7887
8089
  workflow: options.workflow,
7888
8090
  applicability,
7889
8091
  actionContext: buildWorkflowActionContext(manifestEntry, actionData),
8092
+ workflowCard: workflowCardFor(
8093
+ options.workflow,
8094
+ applicability,
8095
+ diagnostics,
8096
+ impactSet
8097
+ ),
7890
8098
  changedFiles: impactSet?.changedFiles ?? [],
7891
8099
  affectedRoutes: impactSet?.affectedRoutes ?? [],
7892
- targetTruthDocs: applicability.state === "ambiguous" ? [] : impactSet?.affectedTruthDocs ?? [],
8100
+ targetTruthDocs: applicability.state === "needs_routing_review" ? [] : impactSet?.affectedTruthDocs ?? [],
7893
8101
  diagnostics,
7894
8102
  checks: {
7895
- required: [...manifestEntry.requiredGates],
8103
+ reviewChecklist: [...manifestEntry.reviewQuestions],
7896
8104
  recommended: [...manifestEntry.positiveTriggers],
7897
8105
  helpers: helperCommandsFor2(options.workflow),
7898
8106
  affectedTests: impactSet?.affectedTests ?? []
@@ -7903,7 +8111,7 @@ var buildWorkflowState = async (cwd, options) => {
7903
8111
  };
7904
8112
 
7905
8113
  // src/cli/handlers.ts
7906
- import fs21 from "fs/promises";
8114
+ import fs20 from "fs/promises";
7907
8115
 
7908
8116
  // src/agents/workflow-helper-validation.ts
7909
8117
  import { parse as parseYaml2 } from "yaml";
@@ -7965,7 +8173,6 @@ var validateEvidenceChecked = (text, errors, checks) => {
7965
8173
  };
7966
8174
  var validateHelperScriptEntries = (entries, requiredHelpers, errors, checks) => {
7967
8175
  if (entries === void 0 || entries.length === 0) {
7968
- errors.push("Helper scripts must include status for optional helpers");
7969
8176
  return;
7970
8177
  }
7971
8178
  const statusPattern = /^(?:-\s*)?([a-z0-9-]+):\s*(?:ran,\s*passed|skipped,\s*\S.*)$/iu;
@@ -7985,7 +8192,7 @@ var validateHelperScriptEntries = (entries, requiredHelpers, errors, checks) =>
7985
8192
  errors.push(`missing Helper scripts status: ${helperId}`);
7986
8193
  }
7987
8194
  }
7988
- if (errors.length === 0) {
8195
+ if (errors.length === 0 && requiredHelpers.length > 0) {
7989
8196
  checks.push(`Helper scripts statuses include ${requiredHelpers.join(", ")}`);
7990
8197
  }
7991
8198
  };
@@ -8014,6 +8221,7 @@ var validateTruthSyncReportText = (text) => {
8014
8221
  ["Changed code reviewed", report.changedCode],
8015
8222
  ["Ownership reviewed", report.ownershipReviewed],
8016
8223
  ["Truth docs updated", report.truthDocsUpdated],
8224
+ ["Decision/rationale captured", report.decisionRationaleCaptured],
8017
8225
  ["Notes", report.notes]
8018
8226
  ]) {
8019
8227
  if (items.length === 0) {
@@ -8029,12 +8237,10 @@ var validateTruthSyncReportText = (text) => {
8029
8237
  "Evidence checked entries include Claim:, Evidence:, and Result: supported | narrowed | removed | blocked"
8030
8238
  );
8031
8239
  }
8032
- validateHelperScriptEntries(
8033
- report.helperScripts,
8034
- ["validate-write-lease"],
8035
- errors,
8036
- checks
8037
- );
8240
+ if (report.syncIntent !== void 0) {
8241
+ checks.push("Sync Intent");
8242
+ }
8243
+ validateHelperScriptEntries(report.helperScripts, [], errors, checks);
8038
8244
  } catch (error) {
8039
8245
  errors.push(error instanceof Error ? error.message : "invalid Truth Sync report");
8040
8246
  }
@@ -8069,11 +8275,6 @@ var validateTruthDocumentReportText = (text) => {
8069
8275
  } else {
8070
8276
  errors.push("missing required section: Evidence checked");
8071
8277
  }
8072
- if (hasLabel(text, "Helper scripts")) {
8073
- checks.push("Helper scripts");
8074
- } else {
8075
- errors.push("missing required section: Helper scripts");
8076
- }
8077
8278
  const truthDocsUpdated = getSection(text, "Truth docs updated");
8078
8279
  const truthDocsCreated = getSection(text, "Truth docs created");
8079
8280
  if (truthDocsUpdated === null && truthDocsCreated === null) {
@@ -8086,7 +8287,7 @@ var validateTruthDocumentReportText = (text) => {
8086
8287
  checks.push("Truth docs updated or created");
8087
8288
  }
8088
8289
  validateEvidenceChecked(text, errors, checks);
8089
- validateHelperScripts(text, ["validate-write-lease"], errors, checks);
8290
+ validateHelperScripts(text, [], errors, checks);
8090
8291
  } else if (status === "blocked") {
8091
8292
  requireBulletSection(text, "Reason", errors, checks);
8092
8293
  }
@@ -8284,7 +8485,7 @@ var invalidWorkflowResult = (command, workflow) => ({
8284
8485
  });
8285
8486
  var readHelperFile = async (filePath, helper) => {
8286
8487
  try {
8287
- return await fs21.readFile(filePath, "utf8");
8488
+ return await fs20.readFile(filePath, "utf8");
8288
8489
  } catch (error) {
8289
8490
  const message = error instanceof Error ? error.message : String(error);
8290
8491
  return { ok: false, helper, errors: [`could not read file: ${message}`] };