truthmark 2.2.0 → 2.2.2

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
@@ -618,14 +618,14 @@ var parseTruthDocumentsFromYaml = (sectionLines, areaName, options) => {
618
618
  const diagnostics = [];
619
619
  const truthDocumentEntries = [];
620
620
  for (const rawEntry of rawEntries) {
621
- const path14 = rawEntry && typeof rawEntry === "object" && "path" in rawEntry ? rawEntry.path : null;
621
+ const path12 = rawEntry && typeof rawEntry === "object" && "path" in rawEntry ? rawEntry.path : null;
622
622
  const kind = rawEntry && typeof rawEntry === "object" && "kind" in rawEntry ? rawEntry.kind : null;
623
623
  const lane = rawEntry && typeof rawEntry === "object" && "lane" in rawEntry ? rawEntry.lane : null;
624
- const inferredKind = typeof path14 === "string" ? inferTruthDocumentKindFromPath(path14, options) : null;
625
- const inferredLane = typeof path14 === "string" ? inferTruthDocumentLaneFromPath(path14, options) : null;
624
+ const inferredKind = typeof path12 === "string" ? inferTruthDocumentKindFromPath(path12, options) : null;
625
+ const inferredLane = typeof path12 === "string" ? inferTruthDocumentLaneFromPath(path12, options) : null;
626
626
  const normalizedKind = isTruthDocumentKind(kind) ? kind : inferredKind;
627
627
  const normalizedLane = lane === "product" || lane === "engineering" ? lane : normalizedKind ? laneForTruthDocumentKind(normalizedKind) : inferredLane;
628
- if (typeof path14 !== "string" || path14.trim().length === 0 || !normalizedKind || !normalizedLane) {
628
+ if (typeof path12 !== "string" || path12.trim().length === 0 || !normalizedKind || !normalizedLane) {
629
629
  diagnostics.push(
630
630
  createAreaDiagnostic(
631
631
  `Area ${areaName} truth_documents entries must include non-empty path plus valid lane and kind fields.`,
@@ -635,7 +635,7 @@ var parseTruthDocumentsFromYaml = (sectionLines, areaName, options) => {
635
635
  continue;
636
636
  }
637
637
  truthDocumentEntries.push({
638
- path: path14.trim(),
638
+ path: path12.trim(),
639
639
  kind: normalizedKind,
640
640
  kindSource: isTruthDocumentKind(kind) ? "explicit" : "inferred",
641
641
  lane: normalizedLane,
@@ -853,7 +853,7 @@ var renderChildAreaTemplate = (config) => {
853
853
  const defaultArea = config.truthmark.routes.defaultArea;
854
854
  const title = titleCase(defaultArea);
855
855
  const truthDocsRoot = truthRoot(config);
856
- const leafTruthDoc = `${truthDocsRoot}/${defaultArea}/overview.md`;
856
+ const bootstrapTruthDoc = `${truthDocsRoot}/${defaultArea}/bootstrap-routing.md`;
857
857
  const templatePath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
858
858
  const sourceOfTruth = resolveRelativePath(
859
859
  templatePath,
@@ -873,16 +873,20 @@ var renderChildAreaTemplate = (config) => {
873
873
  "Truth documents:",
874
874
  "```yaml",
875
875
  "truth_documents:",
876
- ` - path: ${leafTruthDoc}`,
877
- " kind: engineering-behavior",
876
+ ` - path: ${bootstrapTruthDoc}`,
877
+ " kind: engineering-workflow",
878
878
  " lane: engineering",
879
879
  "```",
880
880
  "",
881
+ "This is a provisional bootstrap route. It exists only to make fresh repositories routeable until real product, service, domain, or ownership areas are created.",
882
+ "",
881
883
  "Code surface:",
882
884
  "- src/**",
883
885
  "",
884
886
  "Update truth when:",
885
- "- behavior changes affect repository truth",
887
+ "- this provisional bootstrap route is the only match for a real code surface",
888
+ "- route ownership is still broad, mixed, or ambiguous",
889
+ "- Run Truth Structure before normal Truth Sync so the touched code gets a bounded owner",
886
890
  "",
887
891
  "## Source References",
888
892
  "",
@@ -890,6 +894,103 @@ var renderChildAreaTemplate = (config) => {
890
894
  ""
891
895
  ].join("\n");
892
896
  };
897
+ var renderBootstrapRoutingDocTemplate = (config) => {
898
+ const defaultArea = config.truthmark.routes.defaultArea;
899
+ const title = titleCase(defaultArea);
900
+ const templatePath = `${truthRoot(config)}/${defaultArea}/bootstrap-routing.md`;
901
+ const routePath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
902
+ const routeSource = resolveRelativePath(templatePath, routePath);
903
+ const configSource = resolveRelativePath(
904
+ templatePath,
905
+ ".truthmark/config.yml"
906
+ );
907
+ const today = currentDate();
908
+ return [
909
+ "---",
910
+ "status: active",
911
+ "truth_kind: engineering-workflow",
912
+ `last_reviewed: ${today}`,
913
+ "---",
914
+ "",
915
+ `# ${title} Bootstrap Routing`,
916
+ "",
917
+ "## Purpose",
918
+ "",
919
+ `This doc records the provisional broad route for ${defaultArea}.`,
920
+ "This doc is a bootstrap handoff, not a behavior truth dumping ground.",
921
+ "It is not a substitute for bounded product and engineering truth docs.",
922
+ "",
923
+ "## Scope",
924
+ "",
925
+ "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/**`.",
926
+ "It does not own implementation behavior under that code surface.",
927
+ "",
928
+ "## Current Implementation Behavior",
929
+ "",
930
+ "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.",
931
+ "",
932
+ "## Product Truth Links",
933
+ "",
934
+ "- None. This is an engineering bootstrap handoff for routing setup, not a product promise.",
935
+ "",
936
+ "## Triggers",
937
+ "",
938
+ "- A real code change maps only to this provisional broad route.",
939
+ "- Truth Sync cannot identify a specific behavior-owned route and bounded truth owner.",
940
+ "- A maintainer or agent is onboarding the first real product, service, domain, package, or ownership area.",
941
+ "",
942
+ "## Inputs",
943
+ "",
944
+ "- Current route files under the configured Truthmark route root.",
945
+ "- The touched code, tests, configuration, and existing docs needed to infer the smallest real owner.",
946
+ "- Repository instruction files that exist in the checkout.",
947
+ "",
948
+ "## Execution Model",
949
+ "",
950
+ "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.",
951
+ "",
952
+ "## Steps",
953
+ "",
954
+ "1. Treat this route as provisional and insufficient for normal behavior maintenance.",
955
+ "2. Inspect the touched code/test surface and infer the narrowest durable owner.",
956
+ "3. Create or repair route entries and truth docs for that owner.",
957
+ "4. Leave this bootstrap doc small; do not append behavior details here.",
958
+ "5. Resume Truth Sync only after the touched code resolves to a bounded owner.",
959
+ "",
960
+ "## State, Retry, And Failure Behavior",
961
+ "",
962
+ "If ownership cannot be inferred safely, stop and report manual-review files instead of widening this route or adding generic behavior prose.",
963
+ "",
964
+ "## Outputs",
965
+ "",
966
+ "- Bounded route areas and lane-appropriate truth docs for the touched surface.",
967
+ "- A compact manual handoff report when ownership remains ambiguous.",
968
+ "",
969
+ "## Engineering Decisions",
970
+ "",
971
+ `- 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.`,
972
+ "",
973
+ "## Rationale",
974
+ "",
975
+ "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.",
976
+ "",
977
+ "## Non-Goals",
978
+ "",
979
+ "- This doc is not a repository behavior overview.",
980
+ "- This doc is not a product capability or engineering behavior owner.",
981
+ "- This doc is not a permanent home for claims about files under `src/**`.",
982
+ "",
983
+ "## Maintenance Notes",
984
+ "",
985
+ "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.",
986
+ "",
987
+ "## Source References",
988
+ "",
989
+ `- ${routeSource}`,
990
+ `- ${configSource}`,
991
+ ""
992
+ ].join("\n");
993
+ };
893
994
  var renderTruthRootReadmeTemplate = (config = createDefaultConfig(), lane = "engineering") => {
894
995
  const templatePath = `${lane === "product" ? resolveProductTruthRoot(config) : resolveEngineeringTruthRoot(config)}/README.md`;
895
996
  const sourceOfTruth = resolveRelativePath(
@@ -940,7 +1041,7 @@ var renderTruthDomainReadmeTemplate = (config) => {
940
1041
  "",
941
1042
  "Current leaf docs:",
942
1043
  "",
943
- "- [Overview](overview.md)",
1044
+ "- [Bootstrap routing](bootstrap-routing.md)",
944
1045
  "",
945
1046
  "## Source References",
946
1047
  "",
@@ -948,7 +1049,6 @@ var renderTruthDomainReadmeTemplate = (config) => {
948
1049
  ""
949
1050
  ].join("\n");
950
1051
  };
951
- var BEHAVIOR_DOC_TEMPLATE_PATH = "docs/truthmark/templates/engineering-behavior.md";
952
1052
  var renderTemplateSection = (section) => {
953
1053
  return [
954
1054
  section.heading,
@@ -1363,7 +1463,7 @@ var renderContractDocTemplateFile = () => {
1363
1463
  ]),
1364
1464
  sectionSpec("## Compatibility Rules", [
1365
1465
  "State backward/forward compatibility guarantees, tolerated inputs, deprecation rules, and breaking-change triggers.",
1366
- "Include compatibility tests or review gates that protect the contract."
1466
+ "Include compatibility tests or review questions that protect the contract."
1367
1467
  ]),
1368
1468
  sectionSpec("## Versioning And Migration", [
1369
1469
  "Document version negotiation, schema/API version fields, rollout requirements, migration steps, and rollback expectations.",
@@ -1411,7 +1511,7 @@ var renderWorkflowDocTemplateFile = () => {
1411
1511
  ]),
1412
1512
  sectionSpec("## Execution Model", [
1413
1513
  "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."
1514
+ "State whether the workflow waits for user action, runs in the background, is distributed, or is delegated to another system."
1415
1515
  ]),
1416
1516
  sectionSpec("## Steps", [
1417
1517
  "Capture the current ordered steps or phases at a level useful for maintenance and review.",
@@ -1447,7 +1547,7 @@ var renderOperationsDocTemplateFile = () => {
1447
1547
  ]),
1448
1548
  sectionSpec("## Deployment And Rollback", [
1449
1549
  "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."
1550
+ "Call out manual review points, smoke checks, and post-deploy verification responsibilities."
1451
1551
  ]),
1452
1552
  sectionSpec("## Availability And Observability", [
1453
1553
  "Capture availability expectations, health checks, metrics, logs, traces, alerts, SLO/error-budget signals, and known blind spots.",
@@ -1483,39 +1583,6 @@ var renderTestBehaviorDocTemplateFile = () => {
1483
1583
  ])
1484
1584
  ]);
1485
1585
  };
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
1586
 
1520
1587
  // src/config/command.ts
1521
1588
  var CONFIG_PATH = ".truthmark/config.yml";
@@ -1594,7 +1661,7 @@ var runConfig = async (cwd, options = {}) => {
1594
1661
  };
1595
1662
 
1596
1663
  // src/init/init.ts
1597
- import fs7 from "fs/promises";
1664
+ import fs6 from "fs/promises";
1598
1665
 
1599
1666
  // src/config/load.ts
1600
1667
  import fs4 from "fs/promises";
@@ -1840,22 +1907,6 @@ var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePat
1840
1907
  var truthTemplatePath = (config, fileName) => {
1841
1908
  return `${config.truthmark.paths.templatesRoot}/${fileName}`;
1842
1909
  };
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
1910
  var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTemplate) => {
1860
1911
  const seededResult = await ensureRepoFile(
1861
1912
  rootDir,
@@ -1970,12 +2021,11 @@ var scaffoldHierarchy = async (rootDir, config) => {
1970
2021
  renderProductCapabilityDocTemplateFile()
1971
2022
  )
1972
2023
  );
1973
- const behaviorDocTemplate = await readBehaviorDocTemplate(rootDir, config);
1974
2024
  results.push(
1975
2025
  await ensureRepoFile(
1976
2026
  rootDir,
1977
- `${truthDomainRoot}/overview.md`,
1978
- renderBehaviorLeafDocTemplate(config, behaviorDocTemplate)
2027
+ `${truthDomainRoot}/bootstrap-routing.md`,
2028
+ renderBootstrapRoutingDocTemplate(config)
1979
2029
  )
1980
2030
  );
1981
2031
  return results;
@@ -2029,7 +2079,7 @@ var renderReadOnlyLaneClassificationRuleBlock = (config = defaultAgentConfig(),
2029
2079
  `product-lane ownership belongs under ${productTruthRoot} and describes product promises, boundaries, rationale, decisions, and success criteria`,
2030
2080
  `engineering-lane ownership belongs under ${engineeringTruthRoot} and describes source-backed current realization, contracts, architecture, workflows, operations, or tests`,
2031
2081
  "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",
2082
+ "ambiguous lane ownership should be reported for manual handoff or routed to Truth Structure",
2033
2083
  LANE_INVARIANT
2034
2084
  ].join("\n"),
2035
2085
  indent
@@ -2045,12 +2095,12 @@ var renderLaneClassificationInstructions = (config = defaultAgentConfig()) => {
2045
2095
  const productTruthRoot = resolveProductTruthRoot(config);
2046
2096
  const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2047
2097
  return [
2048
- "Lane classification gate:",
2098
+ "Lane review questions:",
2049
2099
  "- before writing canonical truth docs, classify the request or change as product-lane, engineering-lane, both-lane, or ambiguous",
2050
2100
  `- product-lane writes belong under ${productTruthRoot} and state product promises, boundaries, rationale, decisions, and success criteria`,
2051
2101
  `- engineering-lane writes belong under ${engineeringTruthRoot} and state source-backed current realization, contracts, architecture, workflows, operations, or tests`,
2052
2102
  "- 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",
2103
+ "- ambiguous lane ownership must stop or invoke Truth Structure instead of writing a mixed document",
2054
2104
  `- ${LANE_INVARIANT}`
2055
2105
  ].join("\n");
2056
2106
  };
@@ -2059,7 +2109,7 @@ var EVIDENCE_AUTHORITY_INSTRUCTIONS = [
2059
2109
  "Implementation code and canonical truth docs are inspected evidence for current behavior; they do not silently override workflow write boundaries."
2060
2110
  ].join("\n");
2061
2111
  var REPOSITORY_INTELLIGENCE_INSTRUCTIONS = [
2062
- "Repository intelligence artifacts are optional derived context: RepoIndex, RouteMap, ImpactSet, and ContextPack may guide routing, context selection, and verification planning when available.",
2112
+ "Repository intelligence artifacts are optional derived context: RepoIndex, RouteMap, ImpactSet, and WorkflowState/action context may guide routing, write boundaries, and verification planning when available.",
2063
2113
  "They do not override checkout evidence, canonical truth docs, route files, or workflow write boundaries.",
2064
2114
  "If unavailable, inspect any present Truthmark config, route files, source files, truth docs, and tests directly, then report that repository-intelligence artifacts were not generated."
2065
2115
  ].join("\n");
@@ -2073,29 +2123,29 @@ var FEATURE_DOC_TEMPLATE_INSTRUCTIONS = [
2073
2123
  ].join("\n");
2074
2124
  var renderTruthDocOwnershipGateSection = (subject, outcome) => {
2075
2125
  return [
2076
- "Truth-doc ownership gate:",
2126
+ "Truth-doc ownership review:",
2077
2127
  `- before editing or relying on ${subject}, verify each target/source truth doc is a bounded owner for the behavior`,
2078
2128
  "- 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
2129
  `- ${outcome}`,
2080
- "- report Ownership reviewed, Structure required, Truth docs split, Truth docs restructured, or Blocked reason as applicable"
2130
+ "- report Ownership reviewed, Structure required, Truth docs split, Truth docs restructured, or Manual handoff reason as applicable"
2081
2131
  ].join("\n");
2082
2132
  };
2083
2133
  var TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS = [
2084
- "Decision/Rationale preservation gate:",
2134
+ "Decision/Rationale preservation review:",
2085
2135
  "- 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
2136
  "- 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
2137
  "- 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",
2138
+ "- if ownership of a decision or rationale is unclear, stop with manual-review files instead of deleting it or guessing",
2089
2139
  "- 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
2140
  ].join("\n");
2091
2141
  var renderTruthDocRestructureGateSection = (scope) => {
2092
2142
  return [
2093
- "Truth-doc shape repair gate:",
2143
+ "Truth-doc shape repair review:",
2094
2144
  `- ${scope}`,
2095
- "- repair shape in place only after the ownership gate confirms the doc is the right bounded owner",
2145
+ "- repair shape in place only after the ownership review confirms the doc is the right bounded owner",
2096
2146
  "- use Truth Structure for ownership splits; do not treat broad or mixed-owner docs as in-place repair work",
2097
2147
  "- 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",
2148
+ "- preserve supported claims; remove, narrow, or record unsupported or stale claims for manual handoff",
2099
2149
  "- report docs restructured and why a narrow edit was not sufficient"
2100
2150
  ].join("\n");
2101
2151
  };
@@ -2105,26 +2155,26 @@ var ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS = [
2105
2155
  ].join("\n");
2106
2156
  var renderRouteFirstEvidenceGateSection = (subject, noImpactedDocOutcome) => {
2107
2157
  return [
2108
- "Evidence Gate:",
2158
+ "Evidence checklist:",
2109
2159
  `- route-first: map ${subject} to bounded route owners and primary canonical docs`,
2110
2160
  "- review new or changed behavior-bearing claims only in touched docs, route ownership, lane-specific decisions, and rationale",
2111
2161
  "- support claims with primary checkout evidence: implementation, config, routing, generated templates, schemas, or contract definitions",
2112
2162
  "- tests/examples/canonical docs corroborate; they are not sole proof when implementation conflicts",
2113
- "- remove, narrow, or block unsupported claims",
2163
+ "- remove, narrow, or record unsupported claims for manual handoff",
2114
2164
  `- ${noImpactedDocOutcome}`
2115
2165
  ].join("\n");
2116
2166
  };
2117
2167
  var renderTopologyEvidenceGateSection = () => {
2118
2168
  return [
2119
- "Evidence Gate:",
2120
- "- apply the Evidence Gate before finishing when Truth Structure writes routed docs, ownership claims, lane-specific decisions, or rationale",
2169
+ "Evidence checklist:",
2170
+ "- apply the evidence checklist before finishing when Truth Structure writes routed docs, ownership claims, lane-specific decisions, or rationale",
2121
2171
  "- 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"
2172
+ "- tests/examples/canonical docs corroborate; remove, narrow, or record unsupported claims for manual handoff"
2123
2173
  ].join("\n");
2124
2174
  };
2125
2175
  var renderAuditEvidenceGateSection = () => {
2126
2176
  return [
2127
- "Evidence Gate:",
2177
+ "Evidence checklist:",
2128
2178
  "- support each finding and suggested fix with evidence from config, route files, canonical docs, implementation, templates, or tests",
2129
2179
  "- canonical docs are context, not sole proof when implementation conflicts",
2130
2180
  "- remove unsupported findings or mark open questions; validate changed claims if you edit docs"
@@ -2259,22 +2309,15 @@ var renderHierarchySummary = (config) => {
2259
2309
  ].join("\n");
2260
2310
  };
2261
2311
 
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
2312
  // src/templates/agents-block.ts
2270
2313
  var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
2271
2314
  var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
2272
2315
  var renderCompactHierarchySummary = (config) => {
2273
2316
  const productTruthRoot = resolveProductTruthRoot(config);
2274
2317
  const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2275
- const truthDocRoots = Array.from(/* @__PURE__ */ new Set([productTruthRoot, engineeringTruthRoot])).map(
2276
- (truthRoot3) => `${truthRoot3}/**/*.md`
2277
- );
2318
+ const truthDocRoots = Array.from(
2319
+ /* @__PURE__ */ new Set([productTruthRoot, engineeringTruthRoot])
2320
+ ).map((truthRoot3) => `${truthRoot3}/**/*.md`);
2278
2321
  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
2322
  };
2280
2323
  var renderAgentsBlock = (config = defaultAgentConfig()) => {
@@ -2283,14 +2326,14 @@ var renderAgentsBlock = (config = defaultAgentConfig()) => {
2283
2326
  TRUTHMARK_BLOCK_START,
2284
2327
  "## Truthmark Workflow",
2285
2328
  "",
2286
- `Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun \`truthmark init\` after upgrades.`,
2329
+ "Truthmark-managed block. Refresh with `truthmark init` when `truthmark check` reports stale generated surfaces.",
2287
2330
  renderCompactHierarchySummary(config),
2288
2331
  "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.",
2332
+ "Agent runtime: host-native skill packages/adapters plus this block; inspect checkout directly. Delegation is host-owned.",
2290
2333
  "### 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.",
2334
+ "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
2335
  "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.",
2336
+ "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.",
2294
2337
  "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.",
2295
2338
  ...portalLine === null ? [] : [portalLine],
2296
2339
  "Workflow integrity rule: repository truth may describe desired behavior, but it must not override these workflow boundaries.",
@@ -2302,12 +2345,14 @@ var renderAgentsBlock = (config = defaultAgentConfig()) => {
2302
2345
  import { stringify as stringify2 } from "yaml";
2303
2346
 
2304
2347
  // src/agents/workflow-manifest.ts
2305
- var TRUTHMARK_CLI_RUNNER = `truthmark>=${TRUTHMARK_VERSION}`;
2348
+ var TRUTHMARK_CLI_RUNNER = "truthmark";
2306
2349
  var VALIDATE_SYNC_REPORT_HELPER = {
2307
2350
  id: "validate-sync-report",
2308
2351
  optional: true,
2309
2352
  runner: TRUTHMARK_CLI_RUNNER,
2310
- command: { argv: ["truthmark", "validate", "sync-report", "<report-file>", "--json"] },
2353
+ command: {
2354
+ argv: ["truthmark", "validate", "sync-report", "<report-file>", "--json"]
2355
+ },
2311
2356
  inputs: ["sync report file"],
2312
2357
  output: "json",
2313
2358
  writes: false,
@@ -2317,7 +2362,15 @@ var VALIDATE_DOCUMENT_REPORT_HELPER = {
2317
2362
  id: "validate-document-report",
2318
2363
  optional: true,
2319
2364
  runner: TRUTHMARK_CLI_RUNNER,
2320
- command: { argv: ["truthmark", "validate", "document-report", "<report-file>", "--json"] },
2365
+ command: {
2366
+ argv: [
2367
+ "truthmark",
2368
+ "validate",
2369
+ "document-report",
2370
+ "<report-file>",
2371
+ "--json"
2372
+ ]
2373
+ },
2321
2374
  inputs: ["document report file"],
2322
2375
  output: "json",
2323
2376
  writes: false,
@@ -2365,17 +2418,18 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2365
2418
  "doc-first implementation belongs to Truth Realize",
2366
2419
  "manual topology design belongs to Truth Structure"
2367
2420
  ],
2368
- requiredGates: [
2369
- "topology quality",
2421
+ reviewQuestions: [
2422
+ "topology review",
2370
2423
  "truth-doc ownership",
2371
2424
  "lane classification",
2372
2425
  "Decision/Rationale preservation",
2373
2426
  "truth-doc shape repair when restructuring",
2374
- "Evidence Gate"
2427
+ "Evidence checklist"
2375
2428
  ],
2376
2429
  allowedWrites: ["canonical truth docs", "truth routing files"],
2377
2430
  reportSections: [
2378
2431
  "Changed code reviewed",
2432
+ "Sync Intent",
2379
2433
  "Ownership reviewed",
2380
2434
  "Structure required",
2381
2435
  "Truth docs updated",
@@ -2410,13 +2464,13 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2410
2464
  "must not implement functional code",
2411
2465
  "must not patch mixed-owner docs as shape repair"
2412
2466
  ],
2413
- requiredGates: [
2467
+ reviewQuestions: [
2414
2468
  "truth-doc ownership",
2415
2469
  "lane classification",
2416
2470
  "Decision/Rationale preservation",
2417
2471
  "lane split and relationship repair",
2418
2472
  "truth-doc shape repair when restructuring",
2419
- "Evidence Gate"
2473
+ "Evidence checklist"
2420
2474
  ],
2421
2475
  allowedWrites: ["truth routing files", "starter canonical truth docs"],
2422
2476
  reportSections: [
@@ -2453,11 +2507,11 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2453
2507
  "must not edit functional code",
2454
2508
  "must not repair mixed-owner docs in place"
2455
2509
  ],
2456
- requiredGates: [
2510
+ reviewQuestions: [
2457
2511
  "truth-doc ownership",
2458
2512
  "lane classification",
2459
2513
  "Decision/Rationale preservation",
2460
- "Evidence Gate",
2514
+ "Evidence checklist",
2461
2515
  "truth-doc shape repair when restructuring"
2462
2516
  ],
2463
2517
  allowedWrites: ["canonical truth docs", "truth routing files"],
@@ -2495,7 +2549,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2495
2549
  "must not edit truth docs except through follow-up Truth Sync",
2496
2550
  "must not edit truth routing except through follow-up Truth Sync"
2497
2551
  ],
2498
- requiredGates: ["lane classification", "truth-doc ownership"],
2552
+ reviewQuestions: ["lane classification", "truth-doc ownership"],
2499
2553
  allowedWrites: ["functional code"],
2500
2554
  reportSections: ["Truth docs used", "Code updated", "Verification"]
2501
2555
  },
@@ -2512,7 +2566,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2512
2566
  ],
2513
2567
  negativeTriggers: [
2514
2568
  "normal validation or final correctness audit",
2515
- "automatic preflight or finish-time gate",
2569
+ "automatic preflight or finish-time review",
2516
2570
  "request to mutate truth docs, routing, or code"
2517
2571
  ],
2518
2572
  forbiddenAdjacency: [
@@ -2520,10 +2574,10 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2520
2574
  "must not run Truth Sync automatically",
2521
2575
  "must not authorize later edits or issue write leases"
2522
2576
  ],
2523
- requiredGates: [
2577
+ reviewQuestions: [
2524
2578
  "read-only boundary",
2525
2579
  "intended-not-authorized handoff",
2526
- "blocking ambiguity disclosure"
2580
+ "manual handoff questions"
2527
2581
  ],
2528
2582
  allowedWrites: ["none by default"],
2529
2583
  reportSections: [
@@ -2534,7 +2588,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2534
2588
  "Expected write classes",
2535
2589
  "Expected target files",
2536
2590
  "Suggested subagent use",
2537
- "Blocking ambiguity",
2591
+ "Manual handoff questions",
2538
2592
  "Handoff"
2539
2593
  ],
2540
2594
  subagents: ["truth_route_auditor"]
@@ -2559,7 +2613,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2559
2613
  "must not replace ordinary verification",
2560
2614
  "must not silently rewrite docs"
2561
2615
  ],
2562
- requiredGates: ["audit Evidence Gate"],
2616
+ reviewQuestions: ["audit evidence checklist"],
2563
2617
  allowedWrites: ["none by default"],
2564
2618
  reportSections: [
2565
2619
  "Files reviewed",
@@ -2597,11 +2651,11 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2597
2651
  "machine-readable agent context"
2598
2652
  ],
2599
2653
  forbiddenAdjacency: [
2600
- "must not run as a completion gate",
2654
+ "must not run automatically at completion",
2601
2655
  "must not replace Truth Sync, Truth Check, Truth Document, Truth Realize, or Truth Structure",
2602
2656
  "must not write outside the fixed Portal output directory"
2603
2657
  ],
2604
- requiredGates: [
2658
+ reviewQuestions: [
2605
2659
  "manual-only invocation",
2606
2660
  "Portal output containment",
2607
2661
  "Markdown canonical statement",
@@ -2804,7 +2858,7 @@ ${renderLaneClassificationRuleBlock(config)}
2804
2858
  - may write canonical truth docs and ${config.truthmark.paths.routesIndex} or relevant child route files only
2805
2859
  - must not write functional code
2806
2860
  - 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
2861
+ - stop and recommend Truth Structure when routing repair is unsafe, ambiguous, or outside the task boundary
2808
2862
  - keep feature README.md files as indexes rather than truth-document targets
2809
2863
  - create or update bounded leaf truth docs when behavior does not fit an existing leaf doc
2810
2864
  - write product capability/boundary truth under ${config.truthmark.paths.productTruthRoot} when documenting product promise, boundary, rationale, or user/stakeholder value
@@ -2815,7 +2869,7 @@ ${renderLaneClassificationRuleBlock(config)}
2815
2869
  - preserve unrelated authored content
2816
2870
  ${renderTruthDocOwnershipGateSection(
2817
2871
  "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"
2872
+ "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
2873
  )}
2820
2874
  ${TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS}
2821
2875
  ${renderRouteFirstEvidenceGateSection(
@@ -2838,9 +2892,9 @@ Helper status reporting:
2838
2892
  - Helper output is derived evidence and never replaces direct checkout inspection, evidence review, or parent acceptance.
2839
2893
  Parent post-document verification:
2840
2894
  - 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`;
2895
+ - stop on functional code, generated host surfaces, or unrelated diffs caused by document work
2896
+ - 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
2897
+ - verify the final report records ownership review, structure requirement, restructure, routing update, or manual handoff reason when applicable`;
2844
2898
  };
2845
2899
 
2846
2900
  // src/agents/truth-preview.ts
@@ -2877,7 +2931,7 @@ Suggested subagent use:
2877
2931
  - write workers: none in Preview
2878
2932
  - leases needed: none in Preview
2879
2933
 
2880
- Blocking ambiguity:
2934
+ Manual handoff questions:
2881
2935
  - none identified in preview
2882
2936
 
2883
2937
  Handoff:
@@ -2891,7 +2945,7 @@ Invocations: ${TRUTH_PREVIEW_EXPLICIT_INVOCATIONS}
2891
2945
  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
 
@@ -3044,7 +3098,7 @@ Use when a user asks to onboard a new code area into Truthmark, a new package, c
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
@@ -3060,7 +3114,7 @@ Inspect controllers, routes, handlers, services, packages, tests, existing truth
3060
3114
  When topology pressure exists, repair structure before creating or extending truth docs.
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 and reroute into bounded truth docs when safe; otherwise stop with manual-review files"
3064
3118
  )}
3065
3119
  ${TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS}
3066
3120
  Topology pressure signals:
@@ -3134,6 +3188,60 @@ var parseOptionalBulletSection = (source, title) => {
3134
3188
  }
3135
3189
  return parseBulletLines(section);
3136
3190
  };
3191
+ var parseRequiredBulletSection = (source, title) => {
3192
+ const section = findSection(source, title);
3193
+ if (!section) {
3194
+ throw new Error(`${title} section is required.`);
3195
+ }
3196
+ const entries = parseBulletLines(section);
3197
+ if (entries.length === 0) {
3198
+ throw new Error(`${title} must include at least one bullet.`);
3199
+ }
3200
+ return entries;
3201
+ };
3202
+ var formatIntentValues = (values) => values.join(" / ");
3203
+ var renderSyncIntentSection = (intent) => {
3204
+ return [
3205
+ "Sync Intent:",
3206
+ `- Changed code reviewed: ${formatIntentValues(intent.changedCodeReviewed)}`,
3207
+ `- Affected route/truth owner: ${formatIntentValues(intent.affectedRouteOrTruthOwner)}`,
3208
+ `- Target truth docs: ${formatIntentValues(intent.targetTruthDocs)}`,
3209
+ `- Intended update: ${formatIntentValues(intent.intendedUpdate)}`,
3210
+ `- Evidence to verify: ${formatIntentValues(intent.evidenceToVerify)}`,
3211
+ `- User-provided decisions/rationale: ${formatIntentValues(intent.userProvidedDecisionRationale)}`,
3212
+ `- No-update-needed rationale: ${formatIntentValues(intent.noUpdateNeededRationale)}`,
3213
+ `- Blockers: ${formatIntentValues(intent.blockers)}`
3214
+ ].join("\n");
3215
+ };
3216
+ var parseIntentValues = (value) => {
3217
+ return value.split(" / ").map((item) => item.trim()).filter(hasContent);
3218
+ };
3219
+ var parseSyncIntentSection = (source) => {
3220
+ const entries = parseOptionalBulletSection(source, "Sync Intent");
3221
+ if (entries === void 0) {
3222
+ return void 0;
3223
+ }
3224
+ const values = /* @__PURE__ */ new Map();
3225
+ for (const entry of entries) {
3226
+ const separator = entry.indexOf(":");
3227
+ if (separator === -1) {
3228
+ continue;
3229
+ }
3230
+ values.set(entry.slice(0, separator), entry.slice(separator + 1).trim());
3231
+ }
3232
+ return {
3233
+ changedCodeReviewed: parseIntentValues(values.get("Changed code reviewed") ?? ""),
3234
+ affectedRouteOrTruthOwner: parseIntentValues(values.get("Affected route/truth owner") ?? ""),
3235
+ targetTruthDocs: parseIntentValues(values.get("Target truth docs") ?? ""),
3236
+ intendedUpdate: parseIntentValues(values.get("Intended update") ?? ""),
3237
+ evidenceToVerify: parseIntentValues(values.get("Evidence to verify") ?? ""),
3238
+ userProvidedDecisionRationale: parseIntentValues(
3239
+ values.get("User-provided decisions/rationale") ?? ""
3240
+ ),
3241
+ noUpdateNeededRationale: parseIntentValues(values.get("No-update-needed rationale") ?? ""),
3242
+ blockers: parseIntentValues(values.get("Blockers") ?? "")
3243
+ };
3244
+ };
3137
3245
  var isClaimEvidenceResult = (value) => {
3138
3246
  return ["supported", "narrowed", "removed", "blocked"].includes(value);
3139
3247
  };
@@ -3176,8 +3284,13 @@ var renderTruthSyncCompletedReport = (input) => {
3176
3284
  return [
3177
3285
  "Truth Sync: completed",
3178
3286
  renderBulletSection("Changed code reviewed", input.changedCode),
3287
+ ...input.syncIntent === void 0 ? [] : [renderSyncIntentSection(input.syncIntent)],
3179
3288
  renderBulletSection("Ownership reviewed", input.ownershipReviewed),
3180
3289
  renderBulletSection("Truth docs updated", input.truthDocsUpdated),
3290
+ renderBulletSection(
3291
+ "Decision/rationale captured",
3292
+ input.decisionRationaleCaptured ?? ["none provided in task conversation"]
3293
+ ),
3181
3294
  renderClaimEvidenceCheckedSection(input.evidenceChecked),
3182
3295
  ...input.helperScripts === void 0 ? [] : [renderBulletSection("Helper scripts", input.helperScripts)],
3183
3296
  renderBulletSection("Notes", input.notes)
@@ -3188,11 +3301,17 @@ var parseTruthSyncReport = (source) => {
3188
3301
  throw new Error("Only completed Truth Sync reports can be parsed.");
3189
3302
  }
3190
3303
  const helperScripts = parseOptionalBulletSection(source, "Helper scripts");
3304
+ const syncIntent = parseSyncIntentSection(source);
3191
3305
  return {
3192
3306
  status: "completed",
3193
3307
  changedCode: parseBulletSection(source, "Changed code reviewed"),
3308
+ ...syncIntent === void 0 ? {} : { syncIntent },
3194
3309
  ownershipReviewed: parseBulletSection(source, "Ownership reviewed"),
3195
3310
  truthDocsUpdated: parseBulletSection(source, "Truth docs updated"),
3311
+ decisionRationaleCaptured: parseRequiredBulletSection(
3312
+ source,
3313
+ "Decision/rationale captured"
3314
+ ),
3196
3315
  evidenceChecked: parseEvidenceCheckedSection(source),
3197
3316
  ...helperScripts === void 0 ? {} : { helperScripts },
3198
3317
  notes: parseBulletSection(source, "Notes")
@@ -3216,6 +3335,17 @@ var renderTruthSyncBlockedReport = (input) => {
3216
3335
 
3217
3336
  // src/agents/truth-sync.ts
3218
3337
  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.";
3338
+ var renderTruthSyncProductDecisionRuleBlock = (config) => {
3339
+ return renderBulletBlock(
3340
+ [
3341
+ "ask whether a user-visible promise, capability boundary, API contract, acceptance criterion, or explicit user/product evidence changed",
3342
+ `if yes, update or route product truth under ${config.truthmark.paths.productTruthRoot} as well as engineering truth under ${config.truthmark.paths.engineeringTruthRoot}`,
3343
+ `if no, default to engineering truth under ${config.truthmark.paths.engineeringTruthRoot} for internal implementation changes`,
3344
+ "when both lanes change, keep separate product and engineering docs cross-linked through route YAML with realized_by and realizes",
3345
+ "when ownership is ambiguous, stop or route to Truth Structure instead of writing a mixed document"
3346
+ ].join("\n")
3347
+ );
3348
+ };
3219
3349
  var renderTruthSyncProcedureBody = (config = defaultAgentConfig(), options = {}) => {
3220
3350
  const workflow = getTruthmarkWorkflow("truthmark-sync");
3221
3351
  const claudeSubagentMode = options.includeClaudeSubagentMode ? `${renderClaudeSubagentModeSection(
@@ -3245,7 +3375,7 @@ var renderTruthSyncProcedureBody = (config = defaultAgentConfig(), options = {})
3245
3375
  const subagentMode = `${claudeSubagentMode}${codexSubagentMode}${copilotCustomAgentMode}${openCodeSubagentMode}`;
3246
3376
  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
3377
  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.
3378
+ 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
3379
  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
3380
  Parent workflow:
3251
3381
  1. Inspect git status, staged changes, unstaged changes, and untracked files directly.
@@ -3253,16 +3383,27 @@ Parent workflow:
3253
3383
  3. Identify functional-code changes and the nearest truth docs or routing repairs.
3254
3384
  4. Evidence authority:
3255
3385
  ${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:
3386
+ 5. Product truth decision:
3387
+ ${renderTruthSyncProductDecisionRuleBlock(config)}
3388
+ 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.
3389
+ 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.
3390
+ 8. Code verification is parent-owned: follow repository instructions and task context, and report what ran or why it did not run.
3391
+ 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.
3392
+ 10. Fill Sync Intent before editing truth docs or truth routing files:
3393
+ - Changed code reviewed: functional files, tests, configs, generated outputs, or other implementation evidence inspected
3394
+ - Affected route/truth owner: bounded route area or canonical truth owner that maps the change
3395
+ - Target truth docs: docs expected to change, or docs reviewed and left unchanged
3396
+ - Intended update: claim/doc/routing update planned before writing
3397
+ - Evidence to verify: checkout evidence that will support, narrow, remove, or record each claim for manual handoff
3398
+ - User-provided decisions/rationale: decisions, rationale, constraints, tradeoffs, rejection reasons, or scope boundaries from the current task conversation, or "none provided"
3399
+ - No-update-needed rationale: why mapped truth is already current when no truth doc should change
3400
+ - Blockers: missing routing, ambiguous ownership, failed verification, unavailable evidence, or off-boundary write needs
3401
+ 11. Only edit allowed truth docs/routes after Sync Intent is clear; if ownership is ambiguous, stop and recommend Truth Structure instead of guessing.
3402
+ ${subagentMode}Topology review:
3262
3403
  - before updating truth docs, verify the changed code resolves to a specific behavior-owned area and bounded truth owner
3263
3404
  - 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
3405
  - 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
3406
+ - stop and recommend Truth Structure when topology repair is unsafe, ambiguous, or outside the current task boundary
3266
3407
  - report the route files and changed code paths that require structure repair
3267
3408
  - README.md files are indexes, not Truth Sync targets
3268
3409
  - must not append behavior details to a README.md index
@@ -3270,12 +3411,12 @@ ${subagentMode}Topology quality gate:
3270
3411
  - 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
3412
  ${renderTruthDocOwnershipGateSection(
3272
3413
  "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"
3414
+ "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 stop and recommend Truth Structure"
3274
3415
  )}
3275
3416
  ${TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS}
3276
3417
  ${FEATURE_DOC_TEMPLATE_INSTRUCTIONS}
3277
3418
  ${renderTruthDocRestructureGateSection(
3278
- "Truth Sync may restructure only truth docs impacted by the current functional-code change."
3419
+ "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
3420
  )}
3280
3421
  ${ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS}
3281
3422
  ${renderRouteFirstEvidenceGateSection(
@@ -3298,13 +3439,13 @@ ${renderHierarchySummary(config)}
3298
3439
  ${DECISION_TRUTH_INSTRUCTIONS}
3299
3440
  Parent post-sync verification:
3300
3441
  - 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
3442
+ - stop on any unrelated diff caused by the sync step
3443
+ - stop if functional code changed during sync
3444
+ - 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
3304
3445
  - 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
- - verify the updated docs correspond to the reviewed changed-code surface
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`;
3446
+ - verify the updated docs correspond to reviewed checkout evidence, changed-code impact, or a recorded stale-truth correction made within the sync write lease
3447
+ - verify the final report records ownership review, structure requirement, split, restructure, or manual handoff reason when the ownership review applies
3448
+ - 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
3449
  };
3309
3450
 
3310
3451
  // src/agents/write-lease.ts
@@ -3522,7 +3663,7 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3522
3663
  use: () => "Use this skill only when the user explicitly asks to generate or refresh the committed static HTML Truthmark Portal.",
3523
3664
  quickRules: (config) => [
3524
3665
  "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.",
3666
+ "Truthmark Portal is manual-only; never run it automatically at completion and never treat it as Truth Sync.",
3526
3667
  "Markdown remains canonical; generated HTML is non-canonical presentation only.",
3527
3668
  "Read Markdown directly; the workflow does not require the truthmark CLI or package.",
3528
3669
  "Generate committed, generated non-canonical static files for humans.",
@@ -3556,8 +3697,27 @@ ${renderMarkdownExample(
3556
3697
  ${renderMarkdownExample(
3557
3698
  renderTruthSyncCompletedReport({
3558
3699
  changedCode: ["src/auth/session.ts"],
3700
+ syncIntent: {
3701
+ changedCodeReviewed: ["src/auth/session.ts"],
3702
+ affectedRouteOrTruthOwner: [config.truthmark.paths.routesIndex],
3703
+ targetTruthDocs: [
3704
+ `${engineeringTruthRoot}/repository/bootstrap-routing.md`
3705
+ ],
3706
+ intendedUpdate: ["Update session timeout behavior."],
3707
+ evidenceToVerify: [
3708
+ "src/auth/session.ts:12",
3709
+ `${config.truthmark.paths.routesIndex}:11`
3710
+ ],
3711
+ userProvidedDecisionRationale: [
3712
+ "User rationale: session timeout behavior changed for internal implementation consistency"
3713
+ ],
3714
+ noUpdateNeededRationale: ["not applicable; mapped truth is stale"],
3715
+ blockers: ["none"]
3716
+ },
3559
3717
  ownershipReviewed: [config.truthmark.paths.routesIndex],
3560
- truthDocsUpdated: [`${engineeringTruthRoot}/repository/overview.md`],
3718
+ truthDocsUpdated: [
3719
+ `${engineeringTruthRoot}/repository/bootstrap-routing.md`
3720
+ ],
3561
3721
  evidenceChecked: [
3562
3722
  {
3563
3723
  claim: "Session timeout behavior is documented in the mapped repository truth doc.",
@@ -3568,9 +3728,10 @@ ${renderMarkdownExample(
3568
3728
  result: "supported"
3569
3729
  }
3570
3730
  ],
3571
- helperScripts: [
3572
- "validate-write-lease: skipped, no write lease used"
3731
+ decisionRationaleCaptured: [
3732
+ "Placed user rationale in the mapped engineering truth doc under Engineering Decisions/Rationale."
3573
3733
  ],
3734
+ helperScripts: ["validate-write-lease: skipped, no write lease used"],
3574
3735
  notes: ["Updated session timeout behavior."]
3575
3736
  })
3576
3737
  )}
@@ -3643,7 +3804,7 @@ var renderWorkflowSupportParts = (workflowId, config) => {
3643
3804
  var renderSkillSupportFile = (title, body) => {
3644
3805
  return `# ${title}
3645
3806
 
3646
- Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
3807
+ Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
3647
3808
 
3648
3809
  ${body}
3649
3810
  `;
@@ -3667,7 +3828,7 @@ var renderHelperManifest = (helpers) => {
3667
3828
  )
3668
3829
  };
3669
3830
  return [
3670
- `# Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.`,
3831
+ `# Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.`,
3671
3832
  stringify2(manifest, { lineWidth: 0 })
3672
3833
  ].join("\n");
3673
3834
  };
@@ -3678,12 +3839,12 @@ var renderHelperPolicySupport = (helpers) => {
3678
3839
  ).join("\n");
3679
3840
  return renderSkillSupportFile(
3680
3841
  "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.
3842
+ `Optional helper CLI commands may collect deterministic checkout facts or validate artifacts. If the Truthmark CLI is unavailable or cannot return the declared helper output, 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
3843
 
3683
3844
  Runner detection:
3684
- - Check the declared Truthmark CLI runner before invoking a helper.
3845
+ - Check that the declared Truthmark CLI runner is available before invoking a helper.
3685
3846
  - 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.
3847
+ - If unavailable, failing, or returning incompatible output, treat the helper as skipped and use the manual fallback.
3687
3848
  - Do not fail the workflow solely because a helper cannot run.
3688
3849
 
3689
3850
  Available helpers:
@@ -3721,7 +3882,7 @@ var renderWorkflowEntrypoint = (workflowId, config, supportFiles, host) => {
3721
3882
  const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
3722
3883
  const supportFileUsage = (supportFile) => {
3723
3884
  if (supportFile === "support/procedure.md") {
3724
- return "read before edits or detailed auditing; contains core quality gates";
3885
+ return "read before edits or detailed auditing; contains core review questions";
3725
3886
  }
3726
3887
  if (supportFile === "support/report-template.md") {
3727
3888
  return "read before the final report";
@@ -3744,7 +3905,6 @@ name: ${workflowId}
3744
3905
  description: ${workflow.description}
3745
3906
  argument-hint: ${definition.argumentHint}
3746
3907
  user-invocable: true
3747
- truthmark-version: ${TRUTHMARK_VERSION}
3748
3908
  ---
3749
3909
 
3750
3910
  # ${definition.title}
@@ -3865,8 +4025,8 @@ var renderTruthmarkSkillPackage = ({
3865
4025
  }
3866
4026
  return files;
3867
4027
  };
3868
- var normalizeOpenCodePermissionPath = (path14) => {
3869
- const normalized = path14.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
4028
+ var normalizeOpenCodePermissionPath = (path12) => {
4029
+ const normalized = path12.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
3870
4030
  return normalized === "" ? "." : normalized;
3871
4031
  };
3872
4032
  var appendOpenCodePermissionGlob = (root, glob) => {
@@ -3972,7 +4132,7 @@ var renderCodexReadOnlyAgent = ({
3972
4132
  nicknameCandidates,
3973
4133
  developerInstructions
3974
4134
  }) => {
3975
- return `# Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4135
+ return `# Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
3976
4136
  name = ${renderTomlString(name)}
3977
4137
  description = ${renderTomlString(description)}
3978
4138
  sandbox_mode = "read-only"
@@ -3988,7 +4148,7 @@ var renderCodexWriteAgent = ({
3988
4148
  nicknameCandidates,
3989
4149
  developerInstructions
3990
4150
  }) => {
3991
- return `# Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4151
+ return `# Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
3992
4152
  name = ${renderTomlString(name)}
3993
4153
  description = ${renderTomlString(description)}
3994
4154
  sandbox_mode = "workspace-write"
@@ -4010,7 +4170,7 @@ description: ${description}
4010
4170
  tools: [read, search]
4011
4171
  ---
4012
4172
 
4013
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4173
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4014
4174
 
4015
4175
  ${agentInstructions}
4016
4176
  `;
@@ -4026,7 +4186,7 @@ description: ${description}
4026
4186
  tools: [read, search, edit]
4027
4187
  ---
4028
4188
 
4029
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4189
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4030
4190
 
4031
4191
  ${instructions}
4032
4192
  `;
@@ -4044,7 +4204,7 @@ kind: local
4044
4204
  tools: [read_file, grep_search]
4045
4205
  ---
4046
4206
 
4047
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4207
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4048
4208
 
4049
4209
  Manual invocation: @${copilotName}
4050
4210
 
@@ -4063,7 +4223,7 @@ kind: local
4063
4223
  tools: [read_file, grep_search, write_file]
4064
4224
  ---
4065
4225
 
4066
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4226
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4067
4227
 
4068
4228
  Manual invocation: @${copilotName} with an explicit parent write lease.
4069
4229
 
@@ -4082,7 +4242,7 @@ description: ${description}
4082
4242
  tools: Read, Grep, Glob, LS
4083
4243
  ---
4084
4244
 
4085
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4245
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4086
4246
 
4087
4247
  Manual invocation: use the ${copilotName} subagent.
4088
4248
 
@@ -4100,7 +4260,7 @@ description: ${description}
4100
4260
  tools: Read, Grep, Glob, LS, Edit, MultiEdit
4101
4261
  ---
4102
4262
 
4103
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4263
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4104
4264
 
4105
4265
  Manual invocation: use the ${copilotName} subagent with an explicit parent write lease.
4106
4266
 
@@ -4233,7 +4393,7 @@ permission:
4233
4393
  "grep *": allow
4234
4394
  ---
4235
4395
 
4236
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4396
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4237
4397
 
4238
4398
  Manual invocation: @${invocation}
4239
4399
 
@@ -4268,7 +4428,7 @@ ${editAllowRules}
4268
4428
  "git diff*": allow
4269
4429
  ---
4270
4430
 
4271
- # Generated by Truthmark ${TRUTHMARK_VERSION}. Rerun truthmark init after upgrades.
4431
+ # Truthmark-managed generated file. Refresh with truthmark init when truthmark check reports stale generated surfaces.
4272
4432
 
4273
4433
  Manual invocation: @${invocation}
4274
4434
 
@@ -4319,7 +4479,6 @@ policy:
4319
4479
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4320
4480
 
4321
4481
  truthmark:
4322
- version: "${TRUTHMARK_VERSION}"
4323
4482
  refresh_command: "truthmark init"
4324
4483
  `;
4325
4484
  };
@@ -4334,7 +4493,6 @@ policy:
4334
4493
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4335
4494
 
4336
4495
  truthmark:
4337
- version: "${TRUTHMARK_VERSION}"
4338
4496
  refresh_command: "truthmark init"
4339
4497
  `;
4340
4498
  };
@@ -4349,7 +4507,6 @@ policy:
4349
4507
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4350
4508
 
4351
4509
  truthmark:
4352
- version: "${TRUTHMARK_VERSION}"
4353
4510
  refresh_command: "truthmark init"
4354
4511
  `;
4355
4512
  };
@@ -4373,7 +4530,7 @@ Workflow:
4373
4530
  3. ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
4374
4531
  ${renderTruthDocOwnershipGateSection(
4375
4532
  "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"
4533
+ "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
4534
  )}
4378
4535
  4. Update functional code only so implementation matches bounded, current truth claims from the source docs.
4379
4536
  5. Do not edit truth docs or truth routing while realizing those docs.
@@ -4398,7 +4555,6 @@ policy:
4398
4555
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4399
4556
 
4400
4557
  truthmark:
4401
- version: "${TRUTHMARK_VERSION}"
4402
4558
  refresh_command: "truthmark init"
4403
4559
  `;
4404
4560
  };
@@ -4413,7 +4569,6 @@ policy:
4413
4569
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4414
4570
 
4415
4571
  truthmark:
4416
- version: "${TRUTHMARK_VERSION}"
4417
4572
  refresh_command: "truthmark init"
4418
4573
  `;
4419
4574
  };
@@ -4428,7 +4583,6 @@ policy:
4428
4583
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4429
4584
 
4430
4585
  truthmark:
4431
- version: "${TRUTHMARK_VERSION}"
4432
4586
  refresh_command: "truthmark init"
4433
4587
  `;
4434
4588
  };
@@ -4443,7 +4597,6 @@ policy:
4443
4597
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4444
4598
 
4445
4599
  truthmark:
4446
- version: "${TRUTHMARK_VERSION}"
4447
4600
  refresh_command: "truthmark init"
4448
4601
  `;
4449
4602
  };
@@ -4994,8 +5147,8 @@ var geminiFiles = (config, block) => {
4994
5147
  return files;
4995
5148
  };
4996
5149
  var instructionBlockFiles = (paths, block) => {
4997
- return paths.map((path14) => ({
4998
- path: path14,
5150
+ return paths.map((path12) => ({
5151
+ path: path12,
4999
5152
  content: block,
5000
5153
  managedBlock: true
5001
5154
  }));
@@ -5119,16 +5272,16 @@ var upsertManagedBlock = (existingContent, block) => {
5119
5272
 
5120
5273
  ${block}`;
5121
5274
  };
5122
- var writeManagedAgentsFile = async (rootDir, path14 = "AGENTS.md", block) => {
5275
+ var writeManagedAgentsFile = async (rootDir, path12 = "AGENTS.md", block) => {
5123
5276
  let existingContent = null;
5124
5277
  try {
5125
- existingContent = await fs7.readFile(resolveRepoPath(rootDir, path14), "utf8");
5278
+ existingContent = await fs6.readFile(resolveRepoPath(rootDir, path12), "utf8");
5126
5279
  } catch (error) {
5127
5280
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
5128
5281
  throw error;
5129
5282
  }
5130
5283
  }
5131
- return writeRepoFile(rootDir, path14, upsertManagedBlock(existingContent, block));
5284
+ return writeRepoFile(rootDir, path12, upsertManagedBlock(existingContent, block));
5132
5285
  };
5133
5286
  var diagnosticCategoryForPath = (filePath, config) => {
5134
5287
  if (filePath === "AGENTS.md") {
@@ -5220,7 +5373,7 @@ var runInit = async (cwd) => {
5220
5373
  };
5221
5374
 
5222
5375
  // src/checks/branch-scope.ts
5223
- import fs8 from "fs/promises";
5376
+ import fs7 from "fs/promises";
5224
5377
  import fg from "fast-glob";
5225
5378
 
5226
5379
  // src/markdown/hash.ts
@@ -5276,7 +5429,7 @@ var getBranchScopeData = async (cwd) => {
5276
5429
  }
5277
5430
  for (const relativePath of [...relevantFiles].sort()) {
5278
5431
  try {
5279
- const source = await fs8.readFile(resolveWorktreePath(repository, relativePath), "utf8");
5432
+ const source = await fs7.readFile(resolveWorktreePath(repository, relativePath), "utf8");
5280
5433
  relevantFileHashes[relativePath] = hashText(source);
5281
5434
  } catch (error) {
5282
5435
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -5293,14 +5446,14 @@ var getBranchScopeData = async (cwd) => {
5293
5446
  };
5294
5447
 
5295
5448
  // src/checks/authority.ts
5296
- import fs9 from "fs/promises";
5449
+ import fs8 from "fs/promises";
5297
5450
  import fg2 from "fast-glob";
5298
5451
  var looksLikeGlob = (pattern) => {
5299
5452
  return /[*?[\]{}()!+@]/u.test(pattern);
5300
5453
  };
5301
5454
  var pathExists = async (absolutePath) => {
5302
5455
  try {
5303
- await fs9.stat(absolutePath);
5456
+ await fs8.stat(absolutePath);
5304
5457
  return true;
5305
5458
  } catch (error) {
5306
5459
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -5392,10 +5545,55 @@ var checkAuthority = async (rootDir, config) => {
5392
5545
  };
5393
5546
 
5394
5547
  // src/checks/frontmatter.ts
5395
- import fs10 from "fs/promises";
5548
+ import fs9 from "fs/promises";
5549
+
5550
+ // src/markdown/frontmatter.ts
5551
+ import { parse as parse3 } from "yaml";
5552
+ var openingDelimiterPattern = /^(?:\uFEFF)?---[ \t]*(?:\r?\n|$)/u;
5553
+ var closingDelimiterPattern = /^---[ \t]*(?:\r?\n|$)/mu;
5554
+ var asRecord = (value) => {
5555
+ if (value && typeof value === "object" && !Array.isArray(value)) {
5556
+ return value;
5557
+ }
5558
+ return {};
5559
+ };
5560
+ var parseFrontmatter = (source, options = {}) => {
5561
+ const openingMatch = openingDelimiterPattern.exec(source);
5562
+ if (!openingMatch) {
5563
+ return {
5564
+ data: {},
5565
+ content: source.replace(/^\uFEFF/u, "")
5566
+ };
5567
+ }
5568
+ const bodyStart = openingMatch[0].length;
5569
+ const bodyAndContent = source.slice(bodyStart);
5570
+ const closingMatch = closingDelimiterPattern.exec(bodyAndContent);
5571
+ if (!closingMatch || typeof closingMatch.index !== "number") {
5572
+ return {
5573
+ data: {},
5574
+ content: source.replace(/^\uFEFF/u, "")
5575
+ };
5576
+ }
5577
+ const yamlSource = bodyAndContent.slice(0, closingMatch.index);
5578
+ const content = bodyAndContent.slice(closingMatch.index + closingMatch[0].length);
5579
+ let data = {};
5580
+ if (yamlSource.trim().length > 0) {
5581
+ try {
5582
+ data = asRecord(parse3(yamlSource));
5583
+ } catch (error) {
5584
+ if (options.throwOnInvalid) {
5585
+ throw error;
5586
+ }
5587
+ data = {};
5588
+ }
5589
+ }
5590
+ return {
5591
+ data,
5592
+ content
5593
+ };
5594
+ };
5396
5595
 
5397
5596
  // src/markdown/parse.ts
5398
- import matter from "gray-matter";
5399
5597
  import { unified } from "unified";
5400
5598
  import remarkParse from "remark-parse";
5401
5599
  import { visit } from "unist-util-visit";
@@ -5409,7 +5607,7 @@ var isInternalLink = (url) => {
5409
5607
  return url.startsWith("#") || !url.includes("://") && !url.startsWith("mailto:");
5410
5608
  };
5411
5609
  var parseMarkdownDocument = (source) => {
5412
- const parsed = matter(source);
5610
+ const parsed = parseFrontmatter(source);
5413
5611
  const tree = unified().use(remarkParse).parse(parsed.content);
5414
5612
  const headings = [];
5415
5613
  const internalLinks = [];
@@ -5449,9 +5647,10 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
5449
5647
  }
5450
5648
  const absolutePath = resolveRepoPath(rootDir, markdownPath);
5451
5649
  await assertRepoContainment(rootDir, absolutePath);
5452
- const source = await fs10.readFile(absolutePath, "utf8");
5650
+ const source = await fs9.readFile(absolutePath, "utf8");
5453
5651
  let document;
5454
5652
  try {
5653
+ parseFrontmatter(source, { throwOnInvalid: true });
5455
5654
  document = parseMarkdownDocument(source);
5456
5655
  } catch (error) {
5457
5656
  diagnostics.push({
@@ -5538,11 +5737,11 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
5538
5737
  };
5539
5738
 
5540
5739
  // src/checks/links.ts
5541
- import fs11 from "fs/promises";
5740
+ import fs10 from "fs/promises";
5542
5741
  import path5 from "path";
5543
5742
  var pathExists2 = async (absolutePath) => {
5544
5743
  try {
5545
- await fs11.stat(absolutePath);
5744
+ await fs10.stat(absolutePath);
5546
5745
  return true;
5547
5746
  } catch (error) {
5548
5747
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -5558,7 +5757,7 @@ var checkLinks = async (rootDir, markdownPaths) => {
5558
5757
  continue;
5559
5758
  }
5560
5759
  const absolutePath = resolveRepoPath(rootDir, markdownPath);
5561
- const source = await fs11.readFile(absolutePath, "utf8");
5760
+ const source = await fs10.readFile(absolutePath, "utf8");
5562
5761
  let document;
5563
5762
  try {
5564
5763
  document = parseMarkdownDocument(source);
@@ -5600,12 +5799,12 @@ var checkLinks = async (rootDir, markdownPaths) => {
5600
5799
  };
5601
5800
 
5602
5801
  // src/checks/areas.ts
5603
- import fs13 from "fs/promises";
5802
+ import fs12 from "fs/promises";
5604
5803
  import fg4 from "fast-glob";
5605
5804
  import micromatch4 from "micromatch";
5606
5805
 
5607
5806
  // src/routing/area-resolver.ts
5608
- import fs12 from "fs/promises";
5807
+ import fs11 from "fs/promises";
5609
5808
  import fg3 from "fast-glob";
5610
5809
  import micromatch2 from "micromatch";
5611
5810
  var unique = (values) => {
@@ -5656,7 +5855,7 @@ var ensureChildPath = async (rootDir, areaFilesRoot, filePath) => {
5656
5855
  var readRouteFile = async (rootDir, filePath) => {
5657
5856
  try {
5658
5857
  return {
5659
- source: await fs12.readFile(resolveRepoPath(rootDir, filePath), "utf8"),
5858
+ source: await fs11.readFile(resolveRepoPath(rootDir, filePath), "utf8"),
5660
5859
  diagnostic: null
5661
5860
  };
5662
5861
  } catch (error) {
@@ -5964,7 +6163,7 @@ var looksLikeGlob2 = (pattern) => {
5964
6163
  };
5965
6164
  var pathExists3 = async (absolutePath) => {
5966
6165
  try {
5967
- await fs13.stat(absolutePath);
6166
+ await fs12.stat(absolutePath);
5968
6167
  return true;
5969
6168
  } catch (error) {
5970
6169
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -6405,7 +6604,7 @@ var checkAreas = async (rootDir, config) => {
6405
6604
  };
6406
6605
 
6407
6606
  // src/checks/decisions.ts
6408
- import fs14 from "fs/promises";
6607
+ import fs13 from "fs/promises";
6409
6608
  import micromatch5 from "micromatch";
6410
6609
  var PRODUCT_CAPABILITY_REQUIRED_HEADINGS = [
6411
6610
  "Capability Promise",
@@ -6521,7 +6720,7 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
6521
6720
  (filePath) => truthDocumentMap.has(filePath) || isDecisionTruthCandidate(config, filePath)
6522
6721
  ).sort();
6523
6722
  for (const filePath of candidatePaths) {
6524
- const source = await fs14.readFile(
6723
+ const source = await fs13.readFile(
6525
6724
  resolveRepoPath(rootDir, filePath),
6526
6725
  "utf8"
6527
6726
  );
@@ -6563,10 +6762,10 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
6563
6762
  };
6564
6763
 
6565
6764
  // src/checks/generated-surfaces.ts
6566
- import fs15 from "fs/promises";
6765
+ import fs14 from "fs/promises";
6567
6766
  var readOptionalFile = async (rootDir, filePath) => {
6568
6767
  try {
6569
- return await fs15.readFile(resolveRepoPath(rootDir, filePath), "utf8");
6768
+ return await fs14.readFile(resolveRepoPath(rootDir, filePath), "utf8");
6570
6769
  } catch (error) {
6571
6770
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
6572
6771
  return null;
@@ -6588,22 +6787,6 @@ var normalizeGeneratedSurfaceContent = (content) => {
6588
6787
  }
6589
6788
  return content.replace(/\r\n/g, "\n").replace(/\n$/u, "");
6590
6789
  };
6591
- var versionMarkers = (content) => {
6592
- const markers = [];
6593
- const patterns = [
6594
- /truthmark-version:\s*([^\s]+)/gu,
6595
- /Generated by Truthmark\s+([^\s.]+(?:\.[^\s.]+){1,2})/gu,
6596
- /^version:\s*"(\d+\.\d+\.\d+)"\s*$/gmu
6597
- ];
6598
- for (const pattern of patterns) {
6599
- for (const match of content.matchAll(pattern)) {
6600
- if (match[1]) {
6601
- markers.push(match[1]);
6602
- }
6603
- }
6604
- }
6605
- return markers;
6606
- };
6607
6790
  var checkGeneratedSurfaces = async (rootDir, config) => {
6608
6791
  const diagnostics = [];
6609
6792
  for (const surface of renderGeneratedSurfaces(config)) {
@@ -6629,41 +6812,23 @@ var checkGeneratedSurfaces = async (rootDir, config) => {
6629
6812
  file: surface.path
6630
6813
  });
6631
6814
  }
6632
- const versionContent = surface.managedBlock ? comparableContent ?? "" : content;
6633
- const mismatchedVersions = versionMarkers(versionContent).filter(
6634
- (version) => version !== TRUTHMARK_VERSION
6635
- );
6636
- if (mismatchedVersions.length > 0) {
6637
- diagnostics.push({
6638
- category: "generated-surface",
6639
- severity: "review",
6640
- message: `Generated surface ${surface.path} has Truthmark version ${mismatchedVersions[0]} but current version is ${TRUTHMARK_VERSION}; rerun truthmark init.`,
6641
- file: surface.path
6642
- });
6643
- }
6644
6815
  }
6645
6816
  return diagnostics;
6646
6817
  };
6647
6818
 
6648
6819
  // src/impact/build.ts
6649
- import path11 from "path";
6820
+ import path10 from "path";
6650
6821
  import micromatch7 from "micromatch";
6651
6822
 
6652
- // src/repo-index/build.ts
6653
- import fs18 from "fs/promises";
6654
- import path9 from "path";
6655
-
6656
6823
  // src/repo-index/file-tree.ts
6657
- import fs16 from "fs/promises";
6824
+ import fs15 from "fs/promises";
6658
6825
  import path7 from "path";
6659
6826
  import { execa as execa2 } from "execa";
6660
6827
  import fg5 from "fast-glob";
6661
- import matter3 from "gray-matter";
6662
6828
  import micromatch6 from "micromatch";
6663
6829
 
6664
6830
  // src/truth/source-references.ts
6665
6831
  import path6 from "path";
6666
- import matter2 from "gray-matter";
6667
6832
  var repoRootPrefixes = [
6668
6833
  ".codex/",
6669
6834
  ".github/",
@@ -6724,7 +6889,7 @@ var parseSourceReferencesSection = (content) => {
6724
6889
  return references;
6725
6890
  };
6726
6891
  var parseSourceReferences = (source, truthDocPath) => {
6727
- const parsed = matter2(source);
6892
+ const parsed = parseFrontmatter(source);
6728
6893
  const references = /* @__PURE__ */ new Set();
6729
6894
  const frontmatterSourceOfTruth = Array.isArray(parsed.data.source_of_truth) ? parsed.data.source_of_truth : [];
6730
6895
  for (const entry of frontmatterSourceOfTruth) {
@@ -6752,17 +6917,6 @@ var languageByExtension = /* @__PURE__ */ new Map([
6752
6917
  [".yaml", "yaml"],
6753
6918
  [".toml", "toml"]
6754
6919
  ]);
6755
- var sourceExtensions = /* @__PURE__ */ new Set([
6756
- ".ts",
6757
- ".tsx",
6758
- ".js",
6759
- ".jsx",
6760
- ".mjs",
6761
- ".cjs"
6762
- ]);
6763
- var isJavaScriptLikePath = (filePath) => {
6764
- return sourceExtensions.has(path7.posix.extname(filePath));
6765
- };
6766
6920
  var isTestPath = (filePath) => {
6767
6921
  return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path7.posix.basename(filePath));
6768
6922
  };
@@ -6843,7 +6997,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6843
6997
  for (const filePath of discoveredFiles.filter((entry) => !isIgnoredPath(entry, ignore)).sort()) {
6844
6998
  let stat;
6845
6999
  try {
6846
- stat = await fs16.stat(path7.join(rootDir, filePath));
7000
+ stat = await fs15.stat(path7.join(rootDir, filePath));
6847
7001
  } catch (error) {
6848
7002
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
6849
7003
  continue;
@@ -6867,8 +7021,8 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6867
7021
  });
6868
7022
  }
6869
7023
  if (kind === "doc") {
6870
- const source = await fs16.readFile(path7.join(rootDir, filePath), "utf8");
6871
- const parsed = matter3(source);
7024
+ const source = await fs15.readFile(path7.join(rootDir, filePath), "utf8");
7025
+ const parsed = parseFrontmatter(source);
6872
7026
  const markdown = parseMarkdownDocument(parsed.content);
6873
7027
  const title = markdown.headings.find((heading) => heading.depth === 1)?.text ?? null;
6874
7028
  const sourceOfTruth = parseSourceReferences(source, filePath);
@@ -6895,7 +7049,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6895
7049
  };
6896
7050
 
6897
7051
  // src/repo-index/package-metadata.ts
6898
- import fs17 from "fs/promises";
7052
+ import fs16 from "fs/promises";
6899
7053
  import path8 from "path";
6900
7054
  import fg6 from "fast-glob";
6901
7055
  var packageManagerFor = async (rootDir, packageDir) => {
@@ -6908,7 +7062,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
6908
7062
  ];
6909
7063
  for (const [lockfile, manager] of lockfiles) {
6910
7064
  try {
6911
- await fs17.access(path8.join(rootDir, packageDir, lockfile));
7065
+ await fs16.access(path8.join(rootDir, packageDir, lockfile));
6912
7066
  return manager;
6913
7067
  } catch {
6914
7068
  continue;
@@ -6926,7 +7080,7 @@ var discoverPackageMetadata = async (rootDir) => {
6926
7080
  const packages = [];
6927
7081
  for (const packageFile of packageFiles.sort()) {
6928
7082
  const packageDir = path8.posix.dirname(packageFile) === "." ? "" : path8.posix.dirname(packageFile);
6929
- const raw = JSON.parse(await fs17.readFile(path8.join(rootDir, packageFile), "utf8"));
7083
+ const raw = JSON.parse(await fs16.readFile(path8.join(rootDir, packageFile), "utf8"));
6930
7084
  const scripts = raw.scripts && typeof raw.scripts === "object" ? Object.keys(raw.scripts).sort() : [];
6931
7085
  packages.push({
6932
7086
  path: packageFile,
@@ -6998,96 +7152,6 @@ var buildRouteMap = async (rootDir) => {
6998
7152
  };
6999
7153
  };
7000
7154
 
7001
- // src/repo-index/typescript-symbols.ts
7002
- import ts from "typescript";
7003
- var sortStrings = (values) => [...new Set(values)].sort();
7004
- var hasExportModifier = (node) => {
7005
- return Boolean(
7006
- ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
7007
- );
7008
- };
7009
- var declarationName = (node) => {
7010
- if (!node.name || !ts.isIdentifier(node.name)) {
7011
- return null;
7012
- }
7013
- return node.name.text;
7014
- };
7015
- var addExport = (exports, publicSymbols, path14, name, kind) => {
7016
- if (!name) {
7017
- return;
7018
- }
7019
- const entry = { path: path14, name, kind };
7020
- exports.push(entry);
7021
- publicSymbols.push(entry);
7022
- };
7023
- var analyzeTypeScriptSource = (path14, source) => {
7024
- const sourceFile = ts.createSourceFile(path14, source, ts.ScriptTarget.Latest, true);
7025
- const imports = [];
7026
- const exports = [];
7027
- const publicSymbols = [];
7028
- for (const statement of sourceFile.statements) {
7029
- if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
7030
- const imported = [];
7031
- const clause = statement.importClause;
7032
- if (clause?.name) {
7033
- imported.push("default");
7034
- }
7035
- if (clause?.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
7036
- imported.push("*");
7037
- }
7038
- if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
7039
- for (const element of clause.namedBindings.elements) {
7040
- imported.push(element.propertyName?.text ?? element.name.text);
7041
- }
7042
- }
7043
- imports.push({
7044
- from: path14,
7045
- specifier: statement.moduleSpecifier.text,
7046
- imported: sortStrings(imported)
7047
- });
7048
- continue;
7049
- }
7050
- if (ts.isExportDeclaration(statement)) {
7051
- if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
7052
- for (const element of statement.exportClause.elements) {
7053
- addExport(exports, publicSymbols, path14, element.name.text, "re-export");
7054
- }
7055
- }
7056
- continue;
7057
- }
7058
- if (ts.isFunctionDeclaration(statement) && hasExportModifier(statement)) {
7059
- addExport(exports, publicSymbols, path14, declarationName(statement), "function");
7060
- continue;
7061
- }
7062
- if (ts.isClassDeclaration(statement) && hasExportModifier(statement)) {
7063
- addExport(exports, publicSymbols, path14, declarationName(statement), "class");
7064
- continue;
7065
- }
7066
- if (ts.isInterfaceDeclaration(statement) && hasExportModifier(statement)) {
7067
- addExport(exports, publicSymbols, path14, declarationName(statement), "interface");
7068
- continue;
7069
- }
7070
- if (ts.isTypeAliasDeclaration(statement) && hasExportModifier(statement)) {
7071
- addExport(exports, publicSymbols, path14, declarationName(statement), "type");
7072
- continue;
7073
- }
7074
- if (ts.isEnumDeclaration(statement) && hasExportModifier(statement)) {
7075
- addExport(exports, publicSymbols, path14, declarationName(statement), "enum");
7076
- continue;
7077
- }
7078
- if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
7079
- for (const declaration of statement.declarationList.declarations) {
7080
- addExport(exports, publicSymbols, path14, declarationName(declaration), "const");
7081
- }
7082
- }
7083
- }
7084
- return {
7085
- imports: imports.sort((left, right) => left.specifier.localeCompare(right.specifier)),
7086
- exports: exports.sort((left, right) => left.name.localeCompare(right.name)),
7087
- publicSymbols: publicSymbols.sort((left, right) => left.name.localeCompare(right.name))
7088
- };
7089
- };
7090
-
7091
7155
  // src/repo-index/build.ts
7092
7156
  var buildRepoIndex = async (cwd) => {
7093
7157
  const repository = await getGitRepository(cwd);
@@ -7100,20 +7164,7 @@ var buildRepoIndex = async (cwd) => {
7100
7164
  discoverRepoFiles(rootDir, ignore),
7101
7165
  buildRouteMap(rootDir)
7102
7166
  ]);
7103
- const imports = [];
7104
- const exports = [];
7105
- const publicSymbols = [];
7106
7167
  diagnostics.push(...routeMap.diagnostics);
7107
- for (const file of fileTree.files) {
7108
- if (!isJavaScriptLikePath(file.path)) {
7109
- continue;
7110
- }
7111
- const source = await fs18.readFile(path9.join(rootDir, file.path), "utf8");
7112
- const analysis = analyzeTypeScriptSource(file.path, source);
7113
- imports.push(...analysis.imports);
7114
- exports.push(...analysis.exports);
7115
- publicSymbols.push(...analysis.publicSymbols);
7116
- }
7117
7168
  return {
7118
7169
  schemaVersion: "repo-index/v0",
7119
7170
  repository: {
@@ -7125,11 +7176,6 @@ var buildRepoIndex = async (cwd) => {
7125
7176
  files: fileTree.files,
7126
7177
  docs: fileTree.docs,
7127
7178
  tests: fileTree.tests,
7128
- imports: imports.sort((left, right) => `${left.from}:${left.specifier}`.localeCompare(`${right.from}:${right.specifier}`)),
7129
- exports: exports.sort((left, right) => `${left.path}:${left.name}`.localeCompare(`${right.path}:${right.name}`)),
7130
- publicSymbols: publicSymbols.sort(
7131
- (left, right) => `${left.path}:${left.name}`.localeCompare(`${right.path}:${right.name}`)
7132
- ),
7133
7179
  routeMap,
7134
7180
  diagnostics
7135
7181
  };
@@ -7139,8 +7185,8 @@ var buildRepoIndex = async (cwd) => {
7139
7185
  import { execa as execa4 } from "execa";
7140
7186
 
7141
7187
  // src/git/changes.ts
7142
- import fs19 from "fs/promises";
7143
- import path10 from "path";
7188
+ import fs17 from "fs/promises";
7189
+ import path9 from "path";
7144
7190
  import { execa as execa3 } from "execa";
7145
7191
  var normalizePath3 = (filePath) => {
7146
7192
  return filePath.replaceAll("\\", "/").replace(/^\.\//u, "");
@@ -7151,7 +7197,7 @@ var listChangedPaths = async (cwd, args) => {
7151
7197
  };
7152
7198
  var pathExists4 = async (filePath) => {
7153
7199
  try {
7154
- await fs19.access(filePath);
7200
+ await fs17.access(filePath);
7155
7201
  return true;
7156
7202
  } catch {
7157
7203
  return false;
@@ -7195,7 +7241,7 @@ var getUncommittedChanges = async (cwd) => {
7195
7241
  const deletedPathCandidates = /* @__PURE__ */ new Set([...stagedDeletedPaths, ...unstagedDeletedPaths]);
7196
7242
  for (const deletedPath of deletedPathCandidates) {
7197
7243
  const change = getOrCreateChange(changesByPath, deletedPath);
7198
- change.deleted = !await pathExists4(path10.join(rootDir, deletedPath));
7244
+ change.deleted = !await pathExists4(path9.join(rootDir, deletedPath));
7199
7245
  }
7200
7246
  return Array.from(changesByPath.values()).sort((left, right) => {
7201
7247
  return left.path.localeCompare(right.path);
@@ -7279,14 +7325,6 @@ var getChangedFiles = async (cwd, base) => {
7279
7325
  diagnostics
7280
7326
  };
7281
7327
  };
7282
- var readBaseFile = async (cwd, base, filePath) => {
7283
- const repository = await getGitRepository(cwd);
7284
- const result = await execa4("git", ["show", `${base}:${filePath}`], {
7285
- cwd: repository.worktreePath,
7286
- reject: false
7287
- });
7288
- return (result.exitCode ?? 1) === 0 ? result.stdout : null;
7289
- };
7290
7328
 
7291
7329
  // src/impact/build.ts
7292
7330
  var uniqueSorted2 = (values) => [...new Set(values)].sort();
@@ -7304,65 +7342,15 @@ var toImpactRoute = (route) => ({
7304
7342
  truthDocs: [...route.truthDocs].sort(),
7305
7343
  codeSurface: [...route.codeSurface].sort()
7306
7344
  });
7307
- var changedPathSet = (changedFiles) => {
7308
- return new Set(
7309
- changedFiles.flatMap((file) => [file.path, ...file.previousPath ? [file.previousPath] : []])
7310
- );
7311
- };
7312
7345
  var changedFilePaths = (changedFile) => {
7313
7346
  return [changedFile.path, ...changedFile.previousPath ? [changedFile.previousPath] : []];
7314
7347
  };
7315
- var resolveImportPath = (importEdge) => {
7316
- if (!importEdge.specifier.startsWith(".")) {
7317
- return null;
7318
- }
7319
- const basePath = path11.posix.normalize(path11.posix.join(path11.posix.dirname(importEdge.from), importEdge.specifier));
7320
- const withoutExtension = basePath.replace(/\.[cm]?[jt]sx?$/u, "");
7321
- return withoutExtension;
7322
- };
7323
- var importTargetsChangedFile = (importEdge, changedPath) => {
7324
- const resolved = resolveImportPath(importEdge);
7325
- if (!resolved) {
7326
- return false;
7327
- }
7328
- return changedPath.replace(/\.[cm]?[jt]sx?$/u, "") === resolved;
7329
- };
7330
7348
  var pathSegments = (filePath) => filePath.split("/").filter(Boolean);
7331
7349
  var testHintMatchesChangedFile = (hints, changedPath) => {
7332
- const changedBaseName = path11.posix.basename(changedPath);
7350
+ const changedBaseName = path10.posix.basename(changedPath);
7333
7351
  const changedSegments = pathSegments(changedPath);
7334
7352
  return hints.some((hint) => changedBaseName.startsWith(hint) || changedSegments.includes(hint));
7335
7353
  };
7336
- var changedSymbolsFor = async (cwd, base, basePath, currentPath, currentExports) => {
7337
- if (!isJavaScriptLikePath(basePath) && !isJavaScriptLikePath(currentPath)) {
7338
- return [];
7339
- }
7340
- const baseSource = await readBaseFile(cwd, base, basePath);
7341
- const baseExports = baseSource ? analyzeTypeScriptSource(basePath, baseSource).exports : [];
7342
- const currentByName = new Map(currentExports.map((entry) => [entry.name, entry]));
7343
- const baseByName = new Map(baseExports.map((entry) => [entry.name, entry]));
7344
- const changes = [];
7345
- if (basePath !== currentPath) {
7346
- for (const entry of currentExports) {
7347
- changes.push({ path: currentPath, name: entry.name, kind: entry.kind, change: "added" });
7348
- }
7349
- for (const entry of baseExports) {
7350
- changes.push({ path: basePath, name: entry.name, kind: entry.kind, change: "removed" });
7351
- }
7352
- return changes;
7353
- }
7354
- for (const [name, entry] of currentByName) {
7355
- if (!baseByName.has(name)) {
7356
- changes.push({ path: currentPath, name, kind: entry.kind, change: "added" });
7357
- }
7358
- }
7359
- for (const [name, entry] of baseByName) {
7360
- if (!currentByName.has(name)) {
7361
- changes.push({ path: basePath, name, kind: entry.kind, change: "removed" });
7362
- }
7363
- }
7364
- return changes;
7365
- };
7366
7354
  var buildImpactSet = async (cwd, options) => {
7367
7355
  const repository = await getGitRepository(cwd);
7368
7356
  const rootDir = repository.worktreePath;
@@ -7377,7 +7365,6 @@ var buildImpactSet = async (cwd, options) => {
7377
7365
  const affectedRoutes = /* @__PURE__ */ new Map();
7378
7366
  const affectedTruthDocs = [];
7379
7367
  const affectedTests = [];
7380
- const changedPublicSymbols = [];
7381
7368
  const knownTestPaths = new Set(repoIndex.tests.map((test) => test.path));
7382
7369
  for (const changedFile of changedFiles) {
7383
7370
  const routeCandidatePaths = changedFilePaths(changedFile);
@@ -7410,60 +7397,15 @@ var buildImpactSet = async (cwd, options) => {
7410
7397
  file: changedFile.path
7411
7398
  });
7412
7399
  }
7413
- const currentExports = repoIndex.exports.filter((entry) => entry.path === changedFile.path);
7414
- changedPublicSymbols.push(
7415
- ...await changedSymbolsFor(
7416
- rootDir,
7417
- options.base,
7418
- changedFile.previousPath ?? changedFile.path,
7419
- changedFile.path,
7420
- currentExports
7421
- )
7422
- );
7423
7400
  }
7424
7401
  const uniqueAffectedTruthDocs = uniqueSorted2(affectedTruthDocs);
7425
- const changedPaths = changedPathSet(changedFiles);
7426
- for (const symbol of changedPublicSymbols) {
7427
- if (uniqueAffectedTruthDocs.length === 0) {
7428
- diagnostics.push({
7429
- category: "impact",
7430
- severity: "review",
7431
- message: `Changed public symbol ${symbol.name} in ${symbol.path} has no affected truth document.`,
7432
- file: symbol.path,
7433
- data: {
7434
- symbol: symbol.name,
7435
- change: symbol.change
7436
- }
7437
- });
7438
- continue;
7439
- }
7440
- if (!uniqueAffectedTruthDocs.some((truthDoc) => changedPaths.has(truthDoc))) {
7441
- diagnostics.push({
7442
- category: "impact",
7443
- severity: "review",
7444
- message: `Changed public symbol ${symbol.name} in ${symbol.path} has affected truth docs but none were changed in this impact set.`,
7445
- file: symbol.path,
7446
- data: {
7447
- symbol: symbol.name,
7448
- change: symbol.change,
7449
- affectedTruthDocs: uniqueAffectedTruthDocs
7450
- }
7451
- });
7452
- }
7453
- }
7454
7402
  for (const test of repoIndex.tests) {
7455
- const testImports = repoIndex.imports.filter((edge) => edge.from === test.path);
7456
- const importsChangedFile = changedFiles.some(
7457
- (changedFile) => changedFilePaths(changedFile).some(
7458
- (filePath) => testImports.some((importEdge) => importTargetsChangedFile(importEdge, filePath))
7459
- )
7460
- );
7461
7403
  const hintMatchesChangedFile = changedFiles.some(
7462
7404
  (changedFile) => changedFilePaths(changedFile).some(
7463
7405
  (filePath) => testHintMatchesChangedFile(test.targetHints, filePath)
7464
7406
  )
7465
7407
  );
7466
- if (importsChangedFile || hintMatchesChangedFile) {
7408
+ if (hintMatchesChangedFile) {
7467
7409
  affectedTests.push(test.path);
7468
7410
  }
7469
7411
  }
@@ -7477,9 +7419,6 @@ var buildImpactSet = async (cwd, options) => {
7477
7419
  ),
7478
7420
  affectedTruthDocs: uniqueAffectedTruthDocs,
7479
7421
  affectedTests: uniqueSorted2(affectedTests),
7480
- changedPublicSymbols: changedPublicSymbols.sort(
7481
- (left, right) => `${left.path}:${left.name}:${left.change}`.localeCompare(`${right.path}:${right.name}:${right.change}`)
7482
- ),
7483
7422
  diagnostics
7484
7423
  };
7485
7424
  };
@@ -7505,14 +7444,13 @@ var checkFreshness = async (rootDir, _config, _truthDocumentPaths, base) => {
7505
7444
  };
7506
7445
 
7507
7446
  // src/evidence/validate.ts
7508
- import fs21 from "fs/promises";
7447
+ import fs19 from "fs/promises";
7509
7448
  import fg7 from "fast-glob";
7510
7449
 
7511
7450
  // src/evidence/parse.ts
7512
- import fs20 from "fs/promises";
7513
- import path12 from "path";
7514
- import matter4 from "gray-matter";
7515
- import { parse as parse3 } from "yaml";
7451
+ import fs18 from "fs/promises";
7452
+ import path11 from "path";
7453
+ import { parse as parse4 } from "yaml";
7516
7454
  var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
7517
7455
  var topLevelEvidenceMarkerPattern = /^evidence\s*:/imu;
7518
7456
  var toEvidenceReference = (truthDocPath, raw) => {
@@ -7530,8 +7468,8 @@ var toEvidenceReference = (truthDocPath, raw) => {
7530
7468
  };
7531
7469
  };
7532
7470
  var parseEvidenceReferences = async (rootDir, truthDocPath) => {
7533
- const source = await fs20.readFile(path12.join(rootDir, truthDocPath), "utf8");
7534
- const parsed = matter4(source);
7471
+ const source = await fs18.readFile(path11.join(rootDir, truthDocPath), "utf8");
7472
+ const parsed = parseFrontmatter(source);
7535
7473
  const references = [];
7536
7474
  for (const entry of parseSourceReferences(source, truthDocPath)) {
7537
7475
  references.push({
@@ -7545,7 +7483,7 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
7545
7483
  if (!topLevelEvidenceMarkerPattern.test(yamlBlock)) {
7546
7484
  continue;
7547
7485
  }
7548
- const block = parse3(yamlBlock);
7486
+ const block = parse4(yamlBlock);
7549
7487
  const rawEvidence = block && typeof block === "object" && "evidence" in block ? block.evidence : null;
7550
7488
  if (!Array.isArray(rawEvidence)) {
7551
7489
  continue;
@@ -7563,7 +7501,7 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
7563
7501
  // src/evidence/validate.ts
7564
7502
  var pathExists5 = async (filePath) => {
7565
7503
  try {
7566
- await fs21.access(filePath);
7504
+ await fs19.access(filePath);
7567
7505
  return true;
7568
7506
  } catch {
7569
7507
  return false;
@@ -7601,15 +7539,6 @@ var validateGlob = async (rootDir, reference) => {
7601
7539
  });
7602
7540
  return matches.length > 0 ? null : diagnosticFor(reference, `Referenced file pattern ${reference.path} does not match any file.`);
7603
7541
  };
7604
- var validateSymbol = async (rootDir, reference) => {
7605
- if (!reference.symbol || !isJavaScriptLikePath(reference.path)) {
7606
- return null;
7607
- }
7608
- const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7609
- const analysis = analyzeTypeScriptSource(reference.path, source);
7610
- const hasSymbol = analysis.publicSymbols.some((symbol) => symbol.name === reference.symbol);
7611
- return hasSymbol ? null : diagnosticFor(reference, `Evidence symbol ${reference.symbol} was not found in ${reference.path}.`);
7612
- };
7613
7542
  var validateHash = async (rootDir, reference) => {
7614
7543
  if (!reference.contentHash) {
7615
7544
  return null;
@@ -7617,7 +7546,7 @@ var validateHash = async (rootDir, reference) => {
7617
7546
  if (!reference.contentHash.startsWith("sha256:")) {
7618
7547
  return diagnosticFor(reference, `Evidence hash for ${reference.path} must use sha256:.`);
7619
7548
  }
7620
- const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7549
+ const source = await fs19.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7621
7550
  const lines = source.split("\n");
7622
7551
  const startLine = reference.startLine ?? 1;
7623
7552
  const endLine = reference.endLine ?? lines.length;
@@ -7631,7 +7560,7 @@ var validateLineSpan = async (rootDir, reference) => {
7631
7560
  if (reference.startLine === void 0 && reference.endLine === void 0) {
7632
7561
  return null;
7633
7562
  }
7634
- const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7563
+ const source = await fs19.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7635
7564
  const lines = source.split("\n");
7636
7565
  const startLine = reference.startLine ?? 1;
7637
7566
  const endLine = reference.endLine ?? lines.length;
@@ -7650,10 +7579,6 @@ var validateReference = async (rootDir, reference) => {
7650
7579
  diagnostics.push(diagnosticFor(reference, `Referenced file ${reference.path} does not exist.`));
7651
7580
  return diagnostics;
7652
7581
  }
7653
- const symbolDiagnostic = await validateSymbol(rootDir, reference);
7654
- if (symbolDiagnostic) {
7655
- diagnostics.push(symbolDiagnostic);
7656
- }
7657
7582
  const lineSpanDiagnostic = await validateLineSpan(rootDir, reference);
7658
7583
  if (lineSpanDiagnostic) {
7659
7584
  diagnostics.push(lineSpanDiagnostic);
@@ -7910,220 +7835,60 @@ var runCheck = async (cwd, options = {}) => {
7910
7835
  };
7911
7836
  };
7912
7837
 
7913
- // src/context-pack/build.ts
7914
- import fs22 from "fs/promises";
7915
- import path13 from "path";
7916
- import fg8 from "fast-glob";
7917
- var uniqueSorted3 = (values) => [...new Set(values)].sort();
7918
- var repoRootPrefixes2 = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
7919
- var isGlobReference2 = (referencePath) => /[*?[\]{}()]/u.test(referencePath);
7920
- var normalizeDocReferencePath = (docPath, referencePath) => {
7921
- const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
7922
- if (strippedPath.length === 0 || strippedPath.startsWith("/")) {
7923
- return null;
7924
- }
7925
- const isRepoRelative = repoRootPrefixes2.some((prefix) => strippedPath.startsWith(prefix));
7926
- const normalized = isRepoRelative ? path13.posix.normalize(strippedPath) : path13.posix.normalize(path13.posix.join(path13.posix.dirname(docPath), strippedPath));
7927
- return normalized === ".." || normalized.startsWith("../") ? null : normalized;
7928
- };
7929
- var readIfExists = async (rootDir, filePath) => {
7930
- try {
7931
- return await fs22.readFile(path13.join(rootDir, filePath), "utf8");
7932
- } catch {
7933
- return null;
7934
- }
7935
- };
7936
- var boundContent = (content) => {
7937
- const lines = content.split("\n");
7938
- if (lines.length <= 200) {
7939
- return { content, truncated: false };
7940
- }
7941
- return {
7942
- content: [...lines.slice(0, 80), "...", ...lines.slice(-40)].join("\n"),
7943
- truncated: true
7944
- };
7945
- };
7946
- var boundedContent = (filePath, content, warnings) => {
7947
- const bounded = boundContent(content);
7948
- if (!bounded.truncated) {
7949
- return { path: filePath, content, truncated: false };
7950
- }
7951
- warnings.push({
7952
- category: "context-pack",
7953
- severity: "review",
7954
- message: `Context source file ${filePath} was truncated to fit ContextPack v0 bounds.`,
7955
- file: filePath
7956
- });
7957
- return {
7958
- path: filePath,
7959
- content: bounded.content,
7960
- truncated: true
7961
- };
7962
- };
7963
- var documentsFor = async (rootDir, paths, warnings) => {
7964
- const documents = [];
7965
- for (const filePath of uniqueSorted3(paths)) {
7966
- const content = await readIfExists(rootDir, filePath);
7967
- if (content !== null) {
7968
- const bounded = boundContent(content);
7969
- if (bounded.truncated) {
7970
- warnings.push({
7971
- category: "context-pack",
7972
- severity: "review",
7973
- message: `Context truth doc ${filePath} was truncated to fit ContextPack v0 bounds.`,
7974
- file: filePath
7975
- });
7976
- }
7977
- documents.push({ path: filePath, content: bounded.content, truncated: bounded.truncated });
7978
- }
7979
- }
7980
- return documents;
7981
- };
7982
- var sourceFilesFor = async (rootDir, paths, warnings) => {
7983
- const sourceFiles = [];
7984
- for (const filePath of uniqueSorted3(paths)) {
7985
- const content = await readIfExists(rootDir, filePath);
7986
- if (content !== null) {
7987
- sourceFiles.push(boundedContent(filePath, content, warnings));
7988
- }
7989
- }
7990
- return sourceFiles;
7991
- };
7992
- var sourceOfTruthPathsFor = async (rootDir, docs, truthDocPaths) => {
7993
- const selectedTruthDocs = new Set(truthDocPaths);
7994
- const sourcePaths = [];
7995
- for (const doc of docs) {
7996
- if (!selectedTruthDocs.has(doc.path)) {
7997
- continue;
7998
- }
7999
- for (const referencePath of doc.sourceOfTruth) {
8000
- const normalizedPath = normalizeDocReferencePath(doc.path, referencePath);
8001
- if (!normalizedPath) {
8002
- continue;
8003
- }
8004
- if (isGlobReference2(normalizedPath)) {
8005
- sourcePaths.push(
8006
- ...await fg8(normalizedPath, {
8007
- cwd: rootDir,
8008
- dot: true,
8009
- onlyFiles: true,
8010
- followSymbolicLinks: false
8011
- })
8012
- );
8013
- } else {
8014
- sourcePaths.push(normalizedPath);
8015
- }
8016
- }
8017
- }
8018
- return uniqueSorted3(sourcePaths);
8019
- };
8020
- var writePathsFor = (workflow, routeIndexPath, truthDocs, routes) => {
8021
- if (workflow === "truth-sync") {
8022
- return uniqueSorted3([routeIndexPath, ...truthDocs]);
8023
- }
8024
- if (workflow === "truth-document") {
8025
- return uniqueSorted3([routeIndexPath, ...truthDocs]);
8026
- }
8027
- return uniqueSorted3(routes.flatMap((route) => route.codeSurface));
8028
- };
8029
- var testCommandsFor = (affectedTests) => {
8030
- return affectedTests.length === 0 ? ["npm test"] : [`npm test -- ${affectedTests.join(" ")}`];
8031
- };
8032
- var buildContextPack = async (cwd, options) => {
8033
- const repoIndex = await buildRepoIndex(cwd);
8034
- const rootDir = repoIndex.repository.root;
8035
- const loadResult = await loadConfig(rootDir);
8036
- const config = loadResult.config ?? (loadResult.status === "missing" ? createDefaultConfig() : null);
8037
- const impactSet = options.base ? await buildImpactSet(rootDir, { base: options.base }) : null;
8038
- const routeMap = impactSet ? repoIndex.routeMap : repoIndex.routeMap;
8039
- const warnings = loadResult.status === "invalid" ? [...loadResult.diagnostics] : [];
8040
- const truthDocPaths = impactSet?.affectedTruthDocs ?? (options.workflow === "truth-realize" ? [] : routeMap.routes.flatMap((route) => route.truthDocs));
8041
- const contextRoutes = impactSet?.affectedRoutes ?? (options.workflow === "truth-realize" ? [] : routeMap.routes);
8042
- if (options.workflow === "truth-realize" && !impactSet) {
8043
- warnings.push({
8044
- category: "context-pack",
8045
- severity: "review",
8046
- message: "truth-realize requires --base to derive bounded allowed write paths."
8047
- });
8048
- }
8049
- const sourceOfTruthPaths = await sourceOfTruthPathsFor(rootDir, repoIndex.docs, truthDocPaths);
8050
- const sourceFilePaths = uniqueSorted3([
8051
- ...impactSet?.changedFiles.filter((file) => !file.deleted).map((file) => file.path) ?? [],
8052
- ...sourceOfTruthPaths
8053
- ]);
8054
- return {
8055
- schemaVersion: "context-pack/v0",
8056
- workflow: options.workflow,
8057
- base: options.base ?? null,
8058
- impactSet,
8059
- routeMap,
8060
- allowedWritePaths: config === null ? [] : writePathsFor(
8061
- options.workflow,
8062
- config.truthmark.paths.routesIndex,
8063
- truthDocPaths,
8064
- contextRoutes
8065
- ),
8066
- truthDocs: await documentsFor(rootDir, truthDocPaths, warnings),
8067
- sourceFiles: await sourceFilesFor(rootDir, sourceFilePaths, warnings),
8068
- testCommands: testCommandsFor(impactSet?.affectedTests ?? []),
8069
- warnings
8070
- };
8071
- };
8072
-
8073
- // src/context-pack/render.ts
8074
- var renderContextPackMarkdown = (pack) => {
8075
- const lines = [
8076
- `# Truthmark ContextPack (${pack.workflow})`,
8077
- "",
8078
- `Schema: ${pack.schemaVersion}`,
8079
- `Base: ${pack.base ?? "none"}`,
8080
- "",
8081
- "## Allowed Write Paths",
8082
- ...pack.allowedWritePaths.map((filePath) => `- ${filePath}`),
8083
- "",
8084
- "## Truth Docs",
8085
- ...pack.truthDocs.map((doc) => `- ${doc.path}${doc.truncated ? " (truncated)" : ""}`),
8086
- "",
8087
- "## Source Files",
8088
- ...pack.sourceFiles.map((file) => `- ${file.path}${file.truncated ? " (truncated)" : ""}`),
8089
- "",
8090
- "## Test Commands",
8091
- ...pack.testCommands.map((command) => `- ${command}`)
8092
- ];
8093
- return `${lines.join("\n")}
8094
- `;
8095
- };
8096
-
8097
7838
  // src/workflow-state/build.ts
8098
7839
  import { execa as execa5 } from "execa";
8099
7840
 
8100
7841
  // src/workflow-state/action-context.ts
8101
- var uniqueSorted4 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
7842
+ var uniqueSorted3 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
8102
7843
  var helperCommandsFor = (manifestEntry) => (manifestEntry.helpers ?? []).map((helper) => ({
8103
7844
  id: helper.id,
8104
7845
  runner: helper.runner,
8105
7846
  argv: [...helper.command.argv],
8106
7847
  optional: helper.optional
8107
7848
  }));
8108
- var evidenceFor = (manifestEntry) => manifestEntry.requiredGates.filter((gate) => /evidence|ownership|containment/iu.test(gate));
8109
- var baseContext = (manifestEntry, mode, allowedWritePaths, forbiddenWritePaths, writeLeaseRequired) => ({
7849
+ var evidenceFor = (manifestEntry) => manifestEntry.reviewQuestions.filter(
7850
+ (question) => /evidence|ownership|containment/iu.test(question)
7851
+ );
7852
+ var baseContext = (manifestEntry, mode, allowedWritePaths, routeFiles, primaryTruthDocs, candidateStaleTruthDocs, forbiddenWritePaths, writeLeaseRequired) => ({
8110
7853
  mode,
8111
- allowedWritePaths: uniqueSorted4(allowedWritePaths),
8112
- forbiddenWritePaths: uniqueSorted4(forbiddenWritePaths),
8113
- stopConditions: [...manifestEntry.negativeTriggers, ...manifestEntry.forbiddenAdjacency],
8114
- requiredEvidence: evidenceFor(manifestEntry),
7854
+ allowedWritePaths: uniqueSorted3(allowedWritePaths),
7855
+ routeFiles: uniqueSorted3(routeFiles),
7856
+ primaryTruthDocs: uniqueSorted3(primaryTruthDocs),
7857
+ candidateStaleTruthDocs: uniqueSorted3(candidateStaleTruthDocs),
7858
+ forbiddenWritePaths: uniqueSorted3(forbiddenWritePaths),
7859
+ stopConditions: [
7860
+ ...manifestEntry.negativeTriggers,
7861
+ ...manifestEntry.forbiddenAdjacency
7862
+ ],
7863
+ evidencePrompts: uniqueSorted3([
7864
+ ...evidenceFor(manifestEntry),
7865
+ ...candidateStaleTruthDocs.length > 0 ? [
7866
+ "Record checkout evidence and the reason before touching a candidate stale truth doc outside primaryTruthDocs."
7867
+ ] : []
7868
+ ]),
8115
7869
  helperValidationCommands: helperCommandsFor(manifestEntry),
8116
7870
  writeLeaseRequired
8117
7871
  });
8118
7872
  var buildWorkflowActionContext = (manifestEntry, data = {}) => {
8119
7873
  if (manifestEntry.id === "truthmark-preview" || manifestEntry.id === "truthmark-check") {
8120
- return baseContext(manifestEntry, "read-only", [], [], false);
7874
+ return baseContext(manifestEntry, "read-only", [], [], [], [], [], false);
8121
7875
  }
8122
7876
  if (manifestEntry.id === "truthmark-sync" || manifestEntry.id === "truthmark-document") {
7877
+ const routeFiles = data.routeFiles ?? [];
7878
+ const primaryTruthDocs = data.primaryTruthDocs ?? data.truthDocs ?? [];
7879
+ const candidateStaleTruthDocs = data.candidateStaleTruthDocs ?? [];
8123
7880
  return baseContext(
8124
7881
  manifestEntry,
8125
7882
  "truth-doc-write",
8126
- [...data.routeIndexPath ? [data.routeIndexPath] : [], ...data.routeFiles ?? [], ...data.truthDocs ?? []],
7883
+ [
7884
+ ...data.routeIndexPath ? [data.routeIndexPath] : [],
7885
+ ...routeFiles,
7886
+ ...primaryTruthDocs,
7887
+ ...candidateStaleTruthDocs
7888
+ ],
7889
+ routeFiles,
7890
+ primaryTruthDocs,
7891
+ candidateStaleTruthDocs,
8127
7892
  [],
8128
7893
  true
8129
7894
  );
@@ -8137,6 +7902,9 @@ var buildWorkflowActionContext = (manifestEntry, data = {}) => {
8137
7902
  ...data.routeFiles ?? [],
8138
7903
  ...data.starterTruthDocs ?? []
8139
7904
  ],
7905
+ data.routeFiles ?? [],
7906
+ data.starterTruthDocs ?? [],
7907
+ [],
8140
7908
  [],
8141
7909
  true
8142
7910
  );
@@ -8146,6 +7914,9 @@ var buildWorkflowActionContext = (manifestEntry, data = {}) => {
8146
7914
  manifestEntry,
8147
7915
  "code-write",
8148
7916
  data.codeWritePaths ?? [],
7917
+ data.routeFiles ?? [],
7918
+ data.truthDocs ?? [],
7919
+ [],
8149
7920
  [
8150
7921
  ...data.routeIndexPath ? [data.routeIndexPath] : [],
8151
7922
  ...data.routeFiles ?? [],
@@ -8160,6 +7931,9 @@ var buildWorkflowActionContext = (manifestEntry, data = {}) => {
8160
7931
  "portal-write",
8161
7932
  data.portalEnabled && data.portalOutputPath ? [`${data.portalOutputPath}/**`] : [],
8162
7933
  [],
7934
+ [],
7935
+ [],
7936
+ [],
8163
7937
  false
8164
7938
  );
8165
7939
  };
@@ -8171,79 +7945,139 @@ var helperCommandsFor2 = (workflow) => (TRUTHMARK_WORKFLOW_MANIFEST[workflow].he
8171
7945
  argv: [...helper.command.argv],
8172
7946
  optional: helper.optional
8173
7947
  }));
8174
- var uniqueSorted5 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
7948
+ var uniqueSorted4 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
8175
7949
  var isWriteCapable = (workflow) => !["truthmark-preview", "truthmark-check"].includes(workflow);
8176
- var DEFAULT_BASE_CANDIDATES = ["@{upstream}", "origin/main", "main", "origin/master", "master"];
7950
+ var DEFAULT_BASE_CANDIDATES = [
7951
+ "@{upstream}",
7952
+ "origin/main",
7953
+ "main",
7954
+ "origin/master",
7955
+ "master"
7956
+ ];
8177
7957
  var selectComparisonBase = async (rootDir, suppliedBase) => {
8178
7958
  if (suppliedBase) {
8179
7959
  return suppliedBase;
8180
7960
  }
8181
7961
  for (const candidate of DEFAULT_BASE_CANDIDATES) {
8182
- const result = await execa5("git", ["rev-parse", "--verify", `${candidate}^{commit}`], {
8183
- cwd: rootDir,
8184
- reject: false
8185
- });
7962
+ const result = await execa5(
7963
+ "git",
7964
+ ["rev-parse", "--verify", `${candidate}^{commit}`],
7965
+ {
7966
+ cwd: rootDir,
7967
+ reject: false
7968
+ }
7969
+ );
8186
7970
  if ((result.exitCode ?? 1) === 0) {
8187
7971
  return candidate;
8188
7972
  }
8189
7973
  }
8190
7974
  return null;
8191
7975
  };
8192
- var routeFilesFor = (repoIndex) => uniqueSorted5(repoIndex.routeMap.routes.map((route) => route.sourcePath));
7976
+ var routeFilesFor = (repoIndex) => uniqueSorted4(repoIndex.routeMap.routes.map((route) => route.sourcePath));
7977
+ var candidateStaleTruthDocsFor = (indexedTruthDocs, primaryTruthDocs) => {
7978
+ const primary = new Set(primaryTruthDocs);
7979
+ return indexedTruthDocs.filter((truthDoc) => !primary.has(truthDoc));
7980
+ };
8193
7981
  var hasUnmappedFunctionalChange = (impactSet) => impactSet?.diagnostics.some(
8194
- (diagnostic) => diagnostic.category === "impact" && /not mapped to a Truthmark route|no affected truth document/u.test(diagnostic.message)
7982
+ (diagnostic) => diagnostic.category === "impact" && /not mapped to a Truthmark route|no affected truth document/u.test(
7983
+ diagnostic.message
7984
+ )
8195
7985
  ) ?? false;
8196
7986
  var applicabilityFor = (workflow, diagnostics, impactSet) => {
8197
7987
  const reasons = [];
8198
- if (diagnostics.some((diagnostic) => diagnostic.message.includes("Missing .truthmark/config.yml"))) {
7988
+ if (diagnostics.some(
7989
+ (diagnostic) => diagnostic.message.includes("Missing .truthmark/config.yml")
7990
+ )) {
8199
7991
  reasons.push("Missing .truthmark/config.yml.");
8200
- return { state: isWriteCapable(workflow) ? "blocked" : "not_applicable", reasons };
7992
+ return {
7993
+ state: isWriteCapable(workflow) ? "needs_manual_review" : "not_applicable",
7994
+ reasons
7995
+ };
8201
7996
  }
8202
7997
  if (workflow === "truthmark-sync" && !impactSet) {
8203
- reasons.push("truthmark-sync requires --base to derive bounded truth-doc write paths.");
8204
- return { state: "blocked", reasons };
7998
+ reasons.push(
7999
+ "Choose a comparison base with --base <ref> when workflow status cannot infer one automatically."
8000
+ );
8001
+ return { state: "needs_manual_review", reasons };
8205
8002
  }
8206
8003
  if (workflow === "truthmark-realize" && !impactSet) {
8207
- reasons.push("truthmark-realize requires --base to derive bounded allowed write paths.");
8208
- return { state: "blocked", reasons };
8004
+ reasons.push(
8005
+ "Choose a comparison base with --base <ref> so the helper can suggest bounded code-write paths."
8006
+ );
8007
+ return { state: "needs_manual_review", reasons };
8209
8008
  }
8210
8009
  if (hasUnmappedFunctionalChange(impactSet)) {
8211
- reasons.push("Changed functional files have ambiguous or missing Truthmark route ownership.");
8212
- return { state: "ambiguous", reasons };
8010
+ reasons.push(
8011
+ "Changed functional files need route ownership review before truth-doc suggestions are reliable."
8012
+ );
8013
+ return { state: "needs_routing_review", reasons };
8213
8014
  }
8214
8015
  if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
8215
- reasons.push("Existing diagnostics contain errors that block safe workflow execution.");
8216
- return { state: "blocked", reasons };
8016
+ reasons.push(
8017
+ "Existing diagnostics need manual review before using helper write suggestions."
8018
+ );
8019
+ return { state: "needs_manual_review", reasons };
8217
8020
  }
8218
- return { state: "applicable", reasons };
8021
+ return { state: "ready", reasons };
8022
+ };
8023
+ var workflowCardFor = (workflow, applicability, diagnostics, impactSet) => {
8024
+ const helpers = helperCommandsFor2(workflow);
8025
+ const diagnosticQuestions = diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
8026
+ return {
8027
+ affectedFiles: uniqueSorted4(
8028
+ impactSet?.changedFiles.map((file) => file.path) ?? []
8029
+ ),
8030
+ likelyRouteOwners: uniqueSorted4(
8031
+ impactSet?.affectedRoutes.map((route) => route.sourcePath) ?? []
8032
+ ),
8033
+ suggestedTruthDocs: applicability.state === "needs_routing_review" ? [] : uniqueSorted4(impactSet?.affectedTruthDocs ?? []),
8034
+ openQuestions: uniqueSorted4([
8035
+ ...applicability.reasons,
8036
+ ...diagnosticQuestions
8037
+ ]),
8038
+ skippedHelperStatus: helpers.map((helper) => ({
8039
+ helper: helper.id,
8040
+ status: "skipped",
8041
+ reason: "workflow status does not run optional helpers; inspect the checkout directly or run the helper manually when useful."
8042
+ }))
8043
+ };
8219
8044
  };
8220
8045
  var contextDataFor = (workflow, repoIndex, config, impactSet) => {
8221
8046
  if (!config) {
8222
8047
  return {};
8223
8048
  }
8224
8049
  const routeFiles = routeFilesFor(repoIndex);
8225
- const truthDocs = uniqueSorted5(
8226
- impactSet?.affectedTruthDocs ?? repoIndex.routeMap.routes.flatMap((route) => route.truthDocs)
8050
+ const indexedTruthDocs = uniqueSorted4(
8051
+ repoIndex.routeMap.routes.flatMap((route) => route.truthDocs)
8227
8052
  );
8053
+ const primaryTruthDocs = uniqueSorted4(impactSet?.affectedTruthDocs ?? []);
8054
+ const candidateStaleTruthDocs = workflow === "truthmark-sync" ? candidateStaleTruthDocsFor(indexedTruthDocs, primaryTruthDocs) : [];
8055
+ const truthDocs = workflow === "truthmark-sync" ? uniqueSorted4([...primaryTruthDocs, ...candidateStaleTruthDocs]) : uniqueSorted4(impactSet?.affectedTruthDocs ?? indexedTruthDocs);
8228
8056
  return {
8229
8057
  routeIndexPath: config.truthmark.paths.routesIndex,
8230
8058
  routeFiles,
8231
8059
  truthRoot: config.truthmark.paths.truthRoot,
8232
8060
  truthDocs,
8061
+ primaryTruthDocs: workflow === "truthmark-sync" ? primaryTruthDocs : truthDocs,
8062
+ candidateStaleTruthDocs,
8233
8063
  starterTruthDocs: workflow === "truthmark-structure" ? truthDocs : [],
8234
- codeWritePaths: workflow === "truthmark-realize" ? uniqueSorted5(impactSet?.affectedRoutes.flatMap((route) => route.codeSurface) ?? []) : [],
8064
+ codeWritePaths: workflow === "truthmark-realize" ? uniqueSorted4(
8065
+ impactSet?.affectedRoutes.flatMap((route) => route.codeSurface) ?? []
8066
+ ) : [],
8235
8067
  portalEnabled: config.truthmark.generated.portal.enabled,
8236
8068
  portalOutputPath: config.truthmark.paths.portalOutput,
8237
8069
  routes: repoIndex.routeMap.routes
8238
8070
  };
8239
8071
  };
8240
8072
  var nextStepsFor = (workflow, applicability, comparisonBase) => {
8241
- if (applicability.state === "ambiguous") {
8242
- return ["Run Truth Structure or repair route ownership before writing truth docs."];
8073
+ if (applicability.state === "needs_routing_review") {
8074
+ return [
8075
+ "Run Truth Structure or repair route ownership before writing truth docs."
8076
+ ];
8243
8077
  }
8244
- if ((workflow === "truthmark-sync" || workflow === "truthmark-realize") && applicability.state === "blocked" && !comparisonBase) {
8078
+ if ((workflow === "truthmark-sync" || workflow === "truthmark-realize") && applicability.state === "needs_manual_review" && !comparisonBase) {
8245
8079
  return [
8246
- workflow === "truthmark-sync" ? "Rerun with --base <ref> so Truthmark can derive bounded truth-doc write paths." : "Rerun with --base <ref> so Truthmark can derive bounded allowed code-write paths."
8080
+ 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."
8247
8081
  ];
8248
8082
  }
8249
8083
  return [];
@@ -8258,28 +8092,47 @@ var buildWorkflowState = async (cwd, options) => {
8258
8092
  const loadResult = await loadConfig(rootDir);
8259
8093
  const comparisonBase = options.base ? options.base : options.workflow === "truthmark-sync" ? await selectComparisonBase(rootDir) : null;
8260
8094
  const impactSet = comparisonBase ? await buildImpactSet(rootDir, { base: comparisonBase }) : null;
8261
- const checkResult = await runCheck(cwd, comparisonBase ? { base: comparisonBase } : {});
8095
+ const checkResult = await runCheck(
8096
+ cwd,
8097
+ comparisonBase ? { base: comparisonBase } : {}
8098
+ );
8262
8099
  const diagnostics = [
8263
8100
  ...loadResult.diagnostics,
8264
8101
  ...repoIndex.diagnostics,
8265
8102
  ...impactSet?.diagnostics ?? [],
8266
8103
  ...checkResult.diagnostics
8267
8104
  ];
8268
- const applicability = applicabilityFor(options.workflow, diagnostics, impactSet);
8269
- const actionData = applicability.state === "blocked" || applicability.state === "ambiguous" ? {} : contextDataFor(options.workflow, repoIndex, loadResult.config, impactSet);
8105
+ const applicability = applicabilityFor(
8106
+ options.workflow,
8107
+ diagnostics,
8108
+ impactSet
8109
+ );
8110
+ const actionData = applicability.state === "needs_manual_review" || applicability.state === "needs_routing_review" ? {} : contextDataFor(
8111
+ options.workflow,
8112
+ repoIndex,
8113
+ loadResult.config,
8114
+ impactSet
8115
+ );
8270
8116
  return {
8271
8117
  schemaVersion: "truthmark-workflow/v0",
8272
8118
  workflow: options.workflow,
8273
8119
  applicability,
8274
8120
  actionContext: buildWorkflowActionContext(manifestEntry, actionData),
8121
+ workflowCard: workflowCardFor(
8122
+ options.workflow,
8123
+ applicability,
8124
+ diagnostics,
8125
+ impactSet
8126
+ ),
8275
8127
  changedFiles: impactSet?.changedFiles ?? [],
8276
8128
  affectedRoutes: impactSet?.affectedRoutes ?? [],
8277
- targetTruthDocs: applicability.state === "ambiguous" ? [] : impactSet?.affectedTruthDocs ?? [],
8129
+ targetTruthDocs: applicability.state === "needs_routing_review" ? [] : impactSet?.affectedTruthDocs ?? [],
8278
8130
  diagnostics,
8279
8131
  checks: {
8280
- required: [...manifestEntry.requiredGates],
8132
+ reviewChecklist: [...manifestEntry.reviewQuestions],
8281
8133
  recommended: [...manifestEntry.positiveTriggers],
8282
- helpers: helperCommandsFor2(options.workflow)
8134
+ helpers: helperCommandsFor2(options.workflow),
8135
+ affectedTests: impactSet?.affectedTests ?? []
8283
8136
  },
8284
8137
  nextSteps: nextStepsFor(options.workflow, applicability, comparisonBase),
8285
8138
  reportSections: [...manifestEntry.reportSections]
@@ -8287,7 +8140,7 @@ var buildWorkflowState = async (cwd, options) => {
8287
8140
  };
8288
8141
 
8289
8142
  // src/cli/handlers.ts
8290
- import fs23 from "fs/promises";
8143
+ import fs20 from "fs/promises";
8291
8144
 
8292
8145
  // src/agents/workflow-helper-validation.ts
8293
8146
  import { parse as parseYaml2 } from "yaml";
@@ -8398,6 +8251,7 @@ var validateTruthSyncReportText = (text) => {
8398
8251
  ["Changed code reviewed", report.changedCode],
8399
8252
  ["Ownership reviewed", report.ownershipReviewed],
8400
8253
  ["Truth docs updated", report.truthDocsUpdated],
8254
+ ["Decision/rationale captured", report.decisionRationaleCaptured],
8401
8255
  ["Notes", report.notes]
8402
8256
  ]) {
8403
8257
  if (items.length === 0) {
@@ -8413,6 +8267,9 @@ var validateTruthSyncReportText = (text) => {
8413
8267
  "Evidence checked entries include Claim:, Evidence:, and Result: supported | narrowed | removed | blocked"
8414
8268
  );
8415
8269
  }
8270
+ if (report.syncIntent !== void 0) {
8271
+ checks.push("Sync Intent");
8272
+ }
8416
8273
  validateHelperScriptEntries(
8417
8274
  report.helperScripts,
8418
8275
  ["validate-write-lease"],
@@ -8649,12 +8506,6 @@ var runImpact = async (options) => {
8649
8506
  }
8650
8507
  };
8651
8508
  };
8652
- var isContextPackWorkflow = (value) => {
8653
- return value === "truth-sync" || value === "truth-document" || value === "truth-realize";
8654
- };
8655
- var isContextMarkdownFormat = (value) => {
8656
- return value === void 0 || value === "markdown";
8657
- };
8658
8509
  var isTruthmarkWorkflowId = (value) => {
8659
8510
  return typeof value === "string" && TRUTHMARK_WORKFLOW_IDS.includes(value);
8660
8511
  };
@@ -8674,7 +8525,7 @@ var invalidWorkflowResult = (command, workflow) => ({
8674
8525
  });
8675
8526
  var readHelperFile = async (filePath, helper) => {
8676
8527
  try {
8677
- return await fs23.readFile(filePath, "utf8");
8528
+ return await fs20.readFile(filePath, "utf8");
8678
8529
  } catch (error) {
8679
8530
  const message = error instanceof Error ? error.message : String(error);
8680
8531
  return { ok: false, helper, errors: [`could not read file: ${message}`] };
@@ -8699,63 +8550,6 @@ var runValidateWriteLease = async (leaseFile, changedFilesFile) => {
8699
8550
  }
8700
8551
  return validateWriteLeaseText(leaseText, changedText);
8701
8552
  };
8702
- var runContext = async (options) => {
8703
- if (!isContextPackWorkflow(options.workflow)) {
8704
- return {
8705
- command: "context",
8706
- summary: "Truthmark context requires a supported --workflow value.",
8707
- diagnostics: [
8708
- {
8709
- category: "context-pack",
8710
- severity: "error",
8711
- message: "truthmark context requires --workflow truth-sync, truth-document, or truth-realize."
8712
- }
8713
- ]
8714
- };
8715
- }
8716
- if (options.format === "json") {
8717
- return {
8718
- command: "context",
8719
- summary: "Truthmark context no longer supports JSON ContextPack output.",
8720
- diagnostics: [
8721
- {
8722
- category: "context-pack",
8723
- severity: "error",
8724
- message: "JSON ContextPack output was removed in v2; use --format markdown."
8725
- }
8726
- ]
8727
- };
8728
- }
8729
- if (!isContextMarkdownFormat(options.format)) {
8730
- return {
8731
- command: "context",
8732
- summary: "Truthmark context requires a supported --format value.",
8733
- diagnostics: [
8734
- {
8735
- category: "context-pack",
8736
- severity: "error",
8737
- message: "truthmark context supports only --format markdown; JSON ContextPack output was removed in v2."
8738
- }
8739
- ]
8740
- };
8741
- }
8742
- const contextPack = await buildContextPack(process.cwd(), {
8743
- workflow: options.workflow,
8744
- base: options.base
8745
- });
8746
- const diagnostics = contextPack.warnings;
8747
- const summary = `Truthmark context generated ${contextPack.workflow} ContextPack with ${diagnostics.length} warnings.`;
8748
- const markdown = renderContextPackMarkdown(contextPack);
8749
- return {
8750
- command: "context",
8751
- summary,
8752
- diagnostics,
8753
- data: {
8754
- markdown,
8755
- summary
8756
- }
8757
- };
8758
- };
8759
8553
  var runWorkflowStatus = async (options) => {
8760
8554
  if (!isTruthmarkWorkflowId(options.workflow)) {
8761
8555
  return invalidWorkflowResult("workflow status", options.workflow);
@@ -8790,14 +8584,6 @@ var writeResult = (result, options) => {
8790
8584
  `);
8791
8585
  markFailedWhenErrorDiagnosticsExist(result);
8792
8586
  };
8793
- var writeContextResult = (result, options) => {
8794
- if (!options.json && typeof result.data?.markdown === "string") {
8795
- process.stdout.write(result.data.markdown);
8796
- markFailedWhenErrorDiagnosticsExist(result);
8797
- return;
8798
- }
8799
- writeResult(result, options);
8800
- };
8801
8587
  var renderValidationHuman = (result) => {
8802
8588
  if (result.ok === true) {
8803
8589
  return [`${result.helper}: ok`, ...result.checks.map((check) => `- ${check}`)].join("\n");
@@ -8842,7 +8628,7 @@ var buildProgram = () => {
8842
8628
  writeResult(await runCheck2({ base: options.base }), options);
8843
8629
  });
8844
8630
  addJsonOption(
8845
- program.command("index").description("Build the deterministic Truthmark repository index.")
8631
+ program.command("index").description("Inspect derived Truthmark workflow routing metadata for the current checkout.")
8846
8632
  ).action(async (options) => {
8847
8633
  writeResult(await runIndex(), options);
8848
8634
  });
@@ -8851,18 +8637,6 @@ var buildProgram = () => {
8851
8637
  ).action(async (options) => {
8852
8638
  writeResult(await runImpact({ base: options.base }), options);
8853
8639
  });
8854
- addJsonOption(
8855
- program.command("context").description("Generate a bounded workflow context pack.").requiredOption("--workflow <workflow>", "Workflow name: truth-sync, truth-document, or truth-realize").option("--base <ref>", "Base Git ref for impact-backed packs").option("--format <format>", "Output format: markdown", "markdown")
8856
- ).action(async (options) => {
8857
- writeContextResult(
8858
- await runContext({
8859
- workflow: options.workflow,
8860
- base: options.base,
8861
- format: options.format
8862
- }),
8863
- options
8864
- );
8865
- });
8866
8640
  const workflow = program.command("workflow").description("Inspect agent-facing Truthmark workflow state.");
8867
8641
  addJsonOption(
8868
8642
  workflow.command("status").description("Return schema-versioned workflow state for a canonical workflow ID.").option("--workflow <workflow>", "Canonical workflow ID, such as truthmark-sync").option("--base <ref>", "Base Git ref for impact-backed workflow state")