truthmark 2.1.0 → 2.2.1

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
@@ -284,38 +284,11 @@ var truthmarkConfigSchema = {
284
284
  truthmark: {
285
285
  type: "object",
286
286
  additionalProperties: false,
287
- required: ["workspace", "routes", "truth", "templates", "generated"],
287
+ required: ["workspace", "generated"],
288
288
  properties: {
289
289
  workspace: {
290
290
  type: "string"
291
291
  },
292
- routes: {
293
- type: "object",
294
- additionalProperties: false,
295
- required: ["index", "areas", "default_area", "max_delegation_depth"],
296
- properties: {
297
- index: { type: "string" },
298
- areas: { type: "string" },
299
- default_area: { type: "string" },
300
- max_delegation_depth: { type: "integer", const: 1 }
301
- }
302
- },
303
- truth: {
304
- type: "object",
305
- additionalProperties: false,
306
- required: ["root"],
307
- properties: {
308
- root: { type: "string" }
309
- }
310
- },
311
- templates: {
312
- type: "object",
313
- additionalProperties: false,
314
- required: ["root"],
315
- properties: {
316
- root: { type: "string" }
317
- }
318
- },
319
292
  generated: {
320
293
  type: "object",
321
294
  additionalProperties: false,
@@ -375,33 +348,29 @@ var truthmarkConfigSchema = {
375
348
  // src/config/defaults.ts
376
349
  var DEFAULT_TRUTHMARK_WORKSPACE = {
377
350
  workspace: "docs/truthmark",
378
- routes: {
379
- index: "routes/areas.md",
380
- areas: "routes/areas",
381
- default_area: "repository",
382
- max_delegation_depth: 1
383
- },
384
- truth: {
385
- root: "truth"
386
- },
387
- templates: {
388
- root: "templates"
389
- },
390
351
  generated: {
391
352
  portal: {
392
353
  enabled: false
393
354
  }
394
355
  }
395
356
  };
357
+ var DERIVED_TRUTHMARK_PATHS = {
358
+ routesIndex: "routes/areas.md",
359
+ routeAreasRoot: "routes/areas",
360
+ defaultArea: "repository",
361
+ maxDelegationDepth: 1,
362
+ productTruthRoot: "product",
363
+ engineeringTruthRoot: "engineering",
364
+ templatesRoot: "templates",
365
+ portalOutput: "generated/portal",
366
+ portalTemplate: "templates/portal.html"
367
+ };
396
368
  var DEFAULT_INSTRUCTION_TARGETS = ["AGENTS.md"];
397
369
  var createDefaultRawConfig = () => ({
398
370
  version: 2,
399
371
  platforms: [...DEFAULT_PLATFORMS],
400
372
  truthmark: {
401
373
  workspace: DEFAULT_TRUTHMARK_WORKSPACE.workspace,
402
- routes: { ...DEFAULT_TRUTHMARK_WORKSPACE.routes },
403
- truth: { ...DEFAULT_TRUTHMARK_WORKSPACE.truth },
404
- templates: { ...DEFAULT_TRUTHMARK_WORKSPACE.templates },
405
374
  generated: {
406
375
  portal: { ...DEFAULT_TRUTHMARK_WORKSPACE.generated.portal }
407
376
  }
@@ -409,7 +378,7 @@ var createDefaultRawConfig = () => ({
409
378
  instruction_targets: [...DEFAULT_INSTRUCTION_TARGETS],
410
379
  frontmatter: {
411
380
  required: [],
412
- recommended: ["status", "doc_type", "last_reviewed", "source_of_truth"]
381
+ recommended: ["status", "last_reviewed"]
413
382
  },
414
383
  ignore: ["node_modules/**", "vendor/**", "dist/**", "build/**"]
415
384
  });
@@ -419,20 +388,24 @@ var createDefaultConfig = () => ({
419
388
  truthmark: {
420
389
  workspace: DEFAULT_TRUTHMARK_WORKSPACE.workspace,
421
390
  routes: {
422
- index: DEFAULT_TRUTHMARK_WORKSPACE.routes.index,
423
- areas: DEFAULT_TRUTHMARK_WORKSPACE.routes.areas,
424
- defaultArea: DEFAULT_TRUTHMARK_WORKSPACE.routes.default_area,
425
- maxDelegationDepth: DEFAULT_TRUTHMARK_WORKSPACE.routes.max_delegation_depth
391
+ index: DERIVED_TRUTHMARK_PATHS.routesIndex,
392
+ areas: DERIVED_TRUTHMARK_PATHS.routeAreasRoot,
393
+ defaultArea: DERIVED_TRUTHMARK_PATHS.defaultArea,
394
+ maxDelegationDepth: DERIVED_TRUTHMARK_PATHS.maxDelegationDepth
395
+ },
396
+ truth: {
397
+ productRoot: DERIVED_TRUTHMARK_PATHS.productTruthRoot,
398
+ engineeringRoot: DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
426
399
  },
427
- truth: { root: DEFAULT_TRUTHMARK_WORKSPACE.truth.root },
428
- templates: { root: DEFAULT_TRUTHMARK_WORKSPACE.templates.root },
400
+ templates: { root: DERIVED_TRUTHMARK_PATHS.templatesRoot },
429
401
  generated: {
430
402
  portal: { ...DEFAULT_TRUTHMARK_WORKSPACE.generated.portal }
431
403
  },
432
404
  paths: {
433
405
  routesIndex: "docs/truthmark/routes/areas.md",
434
406
  routeAreasRoot: "docs/truthmark/routes/areas",
435
- truthRoot: "docs/truthmark/truth",
407
+ productTruthRoot: "docs/truthmark/product",
408
+ engineeringTruthRoot: "docs/truthmark/engineering",
436
409
  templatesRoot: "docs/truthmark/templates",
437
410
  portalOutput: "docs/truthmark/generated/portal",
438
411
  portalTemplate: "docs/truthmark/templates/portal.html"
@@ -440,14 +413,15 @@ var createDefaultConfig = () => ({
440
413
  controlledPaths: [
441
414
  "docs/truthmark/routes/areas.md",
442
415
  "docs/truthmark/routes/areas/**/*.md",
443
- "docs/truthmark/truth/**/*.md",
416
+ "docs/truthmark/product/**/*.md",
417
+ "docs/truthmark/engineering/**/*.md",
444
418
  "docs/truthmark/templates/*.md"
445
419
  ]
446
420
  },
447
421
  instructionTargets: [...DEFAULT_INSTRUCTION_TARGETS],
448
422
  frontmatter: {
449
423
  required: [],
450
- recommended: ["status", "doc_type", "last_reviewed", "source_of_truth"]
424
+ recommended: ["status", "last_reviewed"]
451
425
  },
452
426
  ignore: ["node_modules/**", "vendor/**", "dist/**", "build/**"]
453
427
  });
@@ -455,14 +429,23 @@ var createDefaultConfig = () => ({
455
429
  // src/routing/areas.ts
456
430
  import { parse } from "yaml";
457
431
  var TRUTH_DOCUMENT_KINDS = [
458
- "behavior",
459
- "contract",
460
- "architecture",
461
- "workflow",
462
- "operations",
463
- "test-behavior"
432
+ "product-capability",
433
+ "engineering-behavior",
434
+ "engineering-contract",
435
+ "engineering-workflow",
436
+ "engineering-architecture",
437
+ "engineering-operations",
438
+ "engineering-test-behavior"
464
439
  ];
465
- var DEFAULT_WORKSPACE_TRUTH_DOCS_ROOT = "docs/truthmark/truth";
440
+ var uniqueSorted = (values) => [...new Set(values)].sort();
441
+ var mergeTruthDocumentEntryRelationships = (first, second) => ({
442
+ ...first,
443
+ realizedBy: uniqueSorted([...first.realizedBy, ...second.realizedBy]),
444
+ realizes: uniqueSorted([...first.realizes, ...second.realizes]),
445
+ dependsOn: uniqueSorted([...first.dependsOn, ...second.dependsOn])
446
+ });
447
+ var DEFAULT_PRODUCT_TRUTH_DOCS_ROOT = "docs/truthmark/product";
448
+ var DEFAULT_ENGINEERING_TRUTH_DOCS_ROOT = "docs/truthmark/engineering";
466
449
  var slugify = (value) => {
467
450
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
468
451
  };
@@ -482,12 +465,54 @@ var isTruthDocumentKind = (value) => {
482
465
  };
483
466
  var inferTruthDocumentKindFromPath = (documentPath, options = {}) => {
484
467
  const normalizedPath = documentPath.replaceAll("\\", "/");
485
- const truthDocsRoot = (options.truthDocsRoot ?? DEFAULT_WORKSPACE_TRUTH_DOCS_ROOT)?.replaceAll("\\", "/").replace(/\/+$/u, "");
486
- if (truthDocsRoot && normalizedPath.startsWith(`${truthDocsRoot}/`)) {
487
- return "behavior";
468
+ const productTruthRoot = (options.productTruthRoot ?? DEFAULT_PRODUCT_TRUTH_DOCS_ROOT)?.replaceAll("\\", "/").replace(/\/+$/u, "");
469
+ const engineeringTruthRoot = (options.engineeringTruthRoot ?? options.truthDocsRoot ?? DEFAULT_ENGINEERING_TRUTH_DOCS_ROOT)?.replaceAll("\\", "/").replace(/\/+$/u, "");
470
+ if (productTruthRoot && normalizedPath.startsWith(`${productTruthRoot}/`)) {
471
+ return "product-capability";
472
+ }
473
+ if (engineeringTruthRoot && normalizedPath.startsWith(`${engineeringTruthRoot}/`)) {
474
+ if (normalizedPath.includes("/contracts/")) return "engineering-contract";
475
+ if (normalizedPath.includes("/workflows/")) return "engineering-workflow";
476
+ if (normalizedPath.includes("/architecture/"))
477
+ return "engineering-architecture";
478
+ if (normalizedPath.includes("/operations/"))
479
+ return "engineering-operations";
480
+ if (normalizedPath.includes("/tests/")) return "engineering-test-behavior";
481
+ return "engineering-behavior";
482
+ }
483
+ return null;
484
+ };
485
+ var inferTruthDocumentLaneFromPath = (documentPath, options = {}) => {
486
+ const normalizedPath = documentPath.replaceAll("\\", "/");
487
+ const productTruthRoot = (options.productTruthRoot ?? DEFAULT_PRODUCT_TRUTH_DOCS_ROOT).replaceAll("\\", "/").replace(/\/+$/u, "");
488
+ const engineeringTruthRoot = (options.engineeringTruthRoot ?? options.truthDocsRoot ?? DEFAULT_ENGINEERING_TRUTH_DOCS_ROOT).replaceAll("\\", "/").replace(/\/+$/u, "");
489
+ if (normalizedPath.startsWith(`${productTruthRoot}/`)) {
490
+ return "product";
491
+ }
492
+ if (normalizedPath.startsWith(`${engineeringTruthRoot}/`)) {
493
+ return "engineering";
488
494
  }
489
495
  return null;
490
496
  };
497
+ var laneForTruthDocumentKind = (kind) => {
498
+ return kind.startsWith("product-") ? "product" : "engineering";
499
+ };
500
+ var docTypeForTruthDocumentKind = (kind) => {
501
+ if (kind.startsWith("product-")) {
502
+ return "product";
503
+ }
504
+ return kind.slice("engineering-".length);
505
+ };
506
+ var parseStringListField = (rawEntry, field) => {
507
+ if (!rawEntry || typeof rawEntry !== "object" || !(field in rawEntry)) {
508
+ return [];
509
+ }
510
+ const value = rawEntry[field];
511
+ if (!Array.isArray(value)) {
512
+ return [];
513
+ }
514
+ return value.filter((entry) => typeof entry === "string");
515
+ };
491
516
  var findTruthDocumentsYamlFenceRange = (sectionLines) => {
492
517
  const trimmedLines = sectionLines.map((line) => line.trim());
493
518
  const openingFenceIndex = trimmedLines.findIndex(
@@ -509,6 +534,7 @@ var parseTruthDocumentsFromList = (sectionLines, areaName, options) => {
509
534
  const truthDocuments = parseListSection(sectionLines);
510
535
  const truthDocumentEntries = truthDocuments.map((documentPath) => {
511
536
  const inferredKind = inferTruthDocumentKindFromPath(documentPath, options);
537
+ const inferredLane = inferTruthDocumentLaneFromPath(documentPath, options);
512
538
  if (!inferredKind) {
513
539
  diagnostics.push(
514
540
  createAreaDiagnostic(
@@ -520,8 +546,13 @@ var parseTruthDocumentsFromList = (sectionLines, areaName, options) => {
520
546
  }
521
547
  return {
522
548
  path: documentPath,
523
- kind: inferredKind ?? "behavior",
524
- kindSource: inferredKind ? "inferred" : "defaulted"
549
+ kind: inferredKind ?? "engineering-behavior",
550
+ kindSource: inferredKind ? "inferred" : "defaulted",
551
+ lane: inferredLane ?? "engineering",
552
+ laneSource: inferredLane ? "inferred" : "defaulted",
553
+ realizedBy: [],
554
+ realizes: [],
555
+ dependsOn: []
525
556
  };
526
557
  });
527
558
  return {
@@ -530,7 +561,7 @@ var parseTruthDocumentsFromList = (sectionLines, areaName, options) => {
530
561
  diagnostics
531
562
  };
532
563
  };
533
- var parseTruthDocumentsFromYaml = (sectionLines, areaName) => {
564
+ var parseTruthDocumentsFromYaml = (sectionLines, areaName, options) => {
534
565
  const yamlFenceRange = findTruthDocumentsYamlFenceRange(sectionLines);
535
566
  if (!yamlFenceRange) {
536
567
  return {
@@ -587,21 +618,31 @@ var parseTruthDocumentsFromYaml = (sectionLines, areaName) => {
587
618
  const diagnostics = [];
588
619
  const truthDocumentEntries = [];
589
620
  for (const rawEntry of rawEntries) {
590
- const path13 = rawEntry && typeof rawEntry === "object" && "path" in rawEntry ? rawEntry.path : null;
621
+ const path12 = rawEntry && typeof rawEntry === "object" && "path" in rawEntry ? rawEntry.path : null;
591
622
  const kind = rawEntry && typeof rawEntry === "object" && "kind" in rawEntry ? rawEntry.kind : null;
592
- if (typeof path13 !== "string" || path13.trim().length === 0 || !isTruthDocumentKind(kind)) {
623
+ const lane = rawEntry && typeof rawEntry === "object" && "lane" in rawEntry ? rawEntry.lane : null;
624
+ const inferredKind = typeof path12 === "string" ? inferTruthDocumentKindFromPath(path12, options) : null;
625
+ const inferredLane = typeof path12 === "string" ? inferTruthDocumentLaneFromPath(path12, options) : null;
626
+ const normalizedKind = isTruthDocumentKind(kind) ? kind : inferredKind;
627
+ const normalizedLane = lane === "product" || lane === "engineering" ? lane : normalizedKind ? laneForTruthDocumentKind(normalizedKind) : inferredLane;
628
+ if (typeof path12 !== "string" || path12.trim().length === 0 || !normalizedKind || !normalizedLane) {
593
629
  diagnostics.push(
594
630
  createAreaDiagnostic(
595
- `Area ${areaName} truth_documents entries must include non-empty path and valid kind fields.`,
631
+ `Area ${areaName} truth_documents entries must include non-empty path plus valid lane and kind fields.`,
596
632
  areaName
597
633
  )
598
634
  );
599
635
  continue;
600
636
  }
601
637
  truthDocumentEntries.push({
602
- path: path13.trim(),
603
- kind,
604
- kindSource: "explicit"
638
+ path: path12.trim(),
639
+ kind: normalizedKind,
640
+ kindSource: isTruthDocumentKind(kind) ? "explicit" : "inferred",
641
+ lane: normalizedLane,
642
+ laneSource: lane === "product" || lane === "engineering" ? "explicit" : "inferred",
643
+ realizedBy: parseStringListField(rawEntry, "realized_by"),
644
+ realizes: parseStringListField(rawEntry, "realizes"),
645
+ dependsOn: parseStringListField(rawEntry, "depends_on")
605
646
  });
606
647
  }
607
648
  return {
@@ -615,7 +656,11 @@ var parseTruthDocumentsSection = (sectionLines, areaName, options) => {
615
656
  if (!yamlFenceRange) {
616
657
  return parseTruthDocumentsFromList(sectionLines, areaName, options);
617
658
  }
618
- const yamlResult = parseTruthDocumentsFromYaml(sectionLines, areaName);
659
+ const yamlResult = parseTruthDocumentsFromYaml(
660
+ sectionLines,
661
+ areaName,
662
+ options
663
+ );
619
664
  if (yamlResult.diagnostics.length > 0 || yamlFenceRange.closingFenceIndex === null) {
620
665
  return yamlResult;
621
666
  }
@@ -697,8 +742,9 @@ var parseAreasMarkdown = (source, options = {}) => {
697
742
  for (const line of lines) {
698
743
  const areaHeadingMatch = line.match(/^\s{0,3}##\s+(.*)$/u);
699
744
  if (areaHeadingMatch) {
745
+ const heading = areaHeadingMatch[1]?.trim() ?? null;
700
746
  flushArea();
701
- currentAreaName = areaHeadingMatch[1]?.trim() ?? null;
747
+ currentAreaName = heading === "Source References" ? null : heading;
702
748
  continue;
703
749
  }
704
750
  if (!currentAreaName) {
@@ -726,7 +772,13 @@ var parseAreasMarkdown = (source, options = {}) => {
726
772
 
727
773
  // src/truth/docs.ts
728
774
  var resolveTruthDocsRoot = (config) => {
729
- return config.truthmark.paths.truthRoot;
775
+ return config.truthmark.paths.engineeringTruthRoot;
776
+ };
777
+ var resolveProductTruthRoot = (config) => {
778
+ return config.truthmark.paths.productTruthRoot;
779
+ };
780
+ var resolveEngineeringTruthRoot = (config) => {
781
+ return config.truthmark.paths.engineeringTruthRoot;
730
782
  };
731
783
 
732
784
  // src/templates/init-files.ts
@@ -737,7 +789,25 @@ var currentDate = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
737
789
  var resolveRelativePath = (fromPath, toPath) => {
738
790
  return asRelativePath(path3.relative(path3.dirname(fromPath), toPath));
739
791
  };
740
- var truthRoot = resolveTruthDocsRoot;
792
+ var truthRoot = resolveEngineeringTruthRoot;
793
+ var renderLaneRootReadmeSummary = (lane) => {
794
+ if (lane === "product") {
795
+ return [
796
+ "Product truth owns capability promises, boundaries, decisions, and acceptance criteria.",
797
+ "Product lane docs state what must be true, why it matters, and what success means."
798
+ ].join(" ");
799
+ }
800
+ return [
801
+ "Engineering truth owns current realization, contracts, architecture, workflows, operations, and tests.",
802
+ "Engineering lane docs describe how the repository currently implements and operates the behavior."
803
+ ].join(" ");
804
+ };
805
+ var renderLaneRootLeafDocGuidance = (lane) => {
806
+ if (lane === "product") {
807
+ return "README.md files are indexes, not Truth Sync targets. Keep product truth in bounded capability docs.";
808
+ }
809
+ return "README.md files are indexes, not Truth Sync targets. Keep engineering truth in bounded behavior, contract, architecture, workflow, operations, and test docs.";
810
+ };
741
811
  var renderConfigTemplate = () => {
742
812
  return stringify(createDefaultRawConfig());
743
813
  };
@@ -757,8 +827,6 @@ var renderHierarchicalAreasIndexTemplate = (config) => {
757
827
  "status: active",
758
828
  "doc_type: route-index",
759
829
  `last_reviewed: ${currentDate()}`,
760
- "source_of_truth:",
761
- ` - ${sourceOfTruth}`,
762
830
  "---",
763
831
  "",
764
832
  "# Truthmark Areas",
@@ -774,6 +842,10 @@ var renderHierarchicalAreasIndexTemplate = (config) => {
774
842
  "Update truth when:",
775
843
  "- behavior changes affect the routed truth documents",
776
844
  "- API contracts or current feature behavior changes",
845
+ "",
846
+ "## Source References",
847
+ "",
848
+ `- ${sourceOfTruth}`,
777
849
  ""
778
850
  ].join("\n");
779
851
  };
@@ -783,14 +855,15 @@ var renderChildAreaTemplate = (config) => {
783
855
  const truthDocsRoot = truthRoot(config);
784
856
  const leafTruthDoc = `${truthDocsRoot}/${defaultArea}/overview.md`;
785
857
  const templatePath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
786
- const sourceOfTruth = resolveRelativePath(templatePath, ".truthmark/config.yml");
858
+ const sourceOfTruth = resolveRelativePath(
859
+ templatePath,
860
+ ".truthmark/config.yml"
861
+ );
787
862
  return [
788
863
  "---",
789
864
  "status: active",
790
865
  "doc_type: area-route",
791
866
  `last_reviewed: ${currentDate()}`,
792
- "source_of_truth:",
793
- ` - ${sourceOfTruth}`,
794
867
  "---",
795
868
  "",
796
869
  `# ${title} Areas`,
@@ -801,7 +874,8 @@ var renderChildAreaTemplate = (config) => {
801
874
  "```yaml",
802
875
  "truth_documents:",
803
876
  ` - path: ${leafTruthDoc}`,
804
- " kind: behavior",
877
+ " kind: engineering-behavior",
878
+ " lane: engineering",
805
879
  "```",
806
880
  "",
807
881
  "Code surface:",
@@ -809,11 +883,15 @@ var renderChildAreaTemplate = (config) => {
809
883
  "",
810
884
  "Update truth when:",
811
885
  "- behavior changes affect repository truth",
886
+ "",
887
+ "## Source References",
888
+ "",
889
+ `- ${sourceOfTruth}`,
812
890
  ""
813
891
  ].join("\n");
814
892
  };
815
- var renderTruthRootReadmeTemplate = (config = createDefaultConfig()) => {
816
- const templatePath = `${truthRoot(config)}/README.md`;
893
+ var renderTruthRootReadmeTemplate = (config = createDefaultConfig(), lane = "engineering") => {
894
+ const templatePath = `${lane === "product" ? resolveProductTruthRoot(config) : resolveEngineeringTruthRoot(config)}/README.md`;
817
895
  const sourceOfTruth = resolveRelativePath(
818
896
  templatePath,
819
897
  config.truthmark.paths.routesIndex
@@ -823,15 +901,19 @@ var renderTruthRootReadmeTemplate = (config = createDefaultConfig()) => {
823
901
  "status: active",
824
902
  "doc_type: index",
825
903
  `last_reviewed: ${currentDate()}`,
826
- "source_of_truth:",
827
- ` - ${sourceOfTruth}`,
828
904
  "---",
829
905
  "",
830
906
  "# Truth Docs",
831
907
  "",
832
908
  "This directory is an index for current truth docs organized by the configured Truthmark hierarchy.",
833
909
  "",
834
- "README.md files are indexes, not Truth Sync targets. Keep bounded truth in leaf docs under `<domain>/<behavior>.md`.",
910
+ renderLaneRootReadmeSummary(lane),
911
+ "",
912
+ renderLaneRootLeafDocGuidance(lane),
913
+ "",
914
+ "## Source References",
915
+ "",
916
+ `- ${sourceOfTruth}`,
835
917
  ""
836
918
  ].join("\n");
837
919
  };
@@ -848,8 +930,6 @@ var renderTruthDomainReadmeTemplate = (config) => {
848
930
  "status: active",
849
931
  "doc_type: index",
850
932
  `last_reviewed: ${currentDate()}`,
851
- "source_of_truth:",
852
- ` - ${sourceOfTruth}`,
853
933
  "---",
854
934
  "",
855
935
  `# ${title} Truth Docs`,
@@ -861,10 +941,14 @@ var renderTruthDomainReadmeTemplate = (config) => {
861
941
  "Current leaf docs:",
862
942
  "",
863
943
  "- [Overview](overview.md)",
944
+ "",
945
+ "## Source References",
946
+ "",
947
+ `- ${sourceOfTruth}`,
864
948
  ""
865
949
  ].join("\n");
866
950
  };
867
- var BEHAVIOR_DOC_TEMPLATE_PATH = "docs/truthmark/templates/behavior-doc.md";
951
+ var BEHAVIOR_DOC_TEMPLATE_PATH = "docs/truthmark/templates/engineering-behavior.md";
868
952
  var renderTemplateSection = (section) => {
869
953
  return [
870
954
  section.heading,
@@ -928,24 +1012,68 @@ var parseTemplateSections = (template) => {
928
1012
  sections
929
1013
  };
930
1014
  };
1015
+ var LEGACY_MANAGED_TEMPLATE_HEADINGS = /* @__PURE__ */ new Map([
1016
+ ["## Current Behavior", "## Current Implementation Behavior"],
1017
+ ["## Source Evidence", "## Source References"]
1018
+ ]);
1019
+ var resolveManagedTemplateHeading = (heading) => {
1020
+ return LEGACY_MANAGED_TEMPLATE_HEADINGS.get(heading) ?? heading;
1021
+ };
1022
+ var stripManagedFrontmatterFields = (preamble) => {
1023
+ if (!preamble.startsWith("---\n")) {
1024
+ return preamble;
1025
+ }
1026
+ const lines = preamble.split("\n");
1027
+ const closingIndex = lines.findIndex(
1028
+ (line, index) => index > 0 && line.trim() === "---"
1029
+ );
1030
+ if (closingIndex < 0) {
1031
+ return preamble;
1032
+ }
1033
+ const fieldsToRemove = /* @__PURE__ */ new Set(["source_of_truth", "doc_type", "truth_lane"]);
1034
+ const keptFrontmatterLines = [];
1035
+ let skippingManagedField = false;
1036
+ for (const line of lines.slice(1, closingIndex)) {
1037
+ const keyMatch = /^([A-Za-z0-9_-]+):(\s|$)/u.exec(line);
1038
+ if (keyMatch) {
1039
+ skippingManagedField = fieldsToRemove.has(keyMatch[1] ?? "");
1040
+ }
1041
+ if (!skippingManagedField) {
1042
+ keptFrontmatterLines.push(line);
1043
+ }
1044
+ }
1045
+ return [
1046
+ "---",
1047
+ ...keptFrontmatterLines,
1048
+ "---",
1049
+ ...lines.slice(closingIndex + 1)
1050
+ ].join("\n").trimEnd();
1051
+ };
931
1052
  var mergeTruthDocTemplate = (existingTemplate, defaultTemplate) => {
932
1053
  if (existingTemplate.trim().length === 0) {
933
1054
  return defaultTemplate;
934
1055
  }
935
1056
  const defaultParsed = parseTemplateSections(defaultTemplate);
936
1057
  const existingParsed = parseTemplateSections(existingTemplate);
937
- const defaultHeadings = new Set(defaultParsed.sections.map((section) => section.heading));
1058
+ const defaultHeadings = new Set(
1059
+ defaultParsed.sections.map((section) => section.heading)
1060
+ );
938
1061
  const customBeforeDefault = /* @__PURE__ */ new Map();
939
1062
  const trailingCustomSections = [];
940
1063
  existingParsed.sections.forEach((section, index) => {
941
- if (defaultHeadings.has(section.heading)) {
1064
+ if (defaultHeadings.has(resolveManagedTemplateHeading(section.heading))) {
942
1065
  return;
943
1066
  }
944
- const nextDefaultSection = existingParsed.sections.slice(index + 1).find((candidate) => defaultHeadings.has(candidate.heading));
1067
+ const nextDefaultSection = existingParsed.sections.slice(index + 1).find(
1068
+ (candidate) => defaultHeadings.has(resolveManagedTemplateHeading(candidate.heading))
1069
+ );
945
1070
  if (nextDefaultSection) {
946
- const bucket = customBeforeDefault.get(nextDefaultSection.heading) ?? [];
1071
+ const nextDefaultHeading = resolveManagedTemplateHeading(
1072
+ nextDefaultSection.heading
1073
+ );
1074
+ const bucket = customBeforeDefault.get(nextDefaultHeading) ?? [];
947
1075
  bucket.push(section);
948
- customBeforeDefault.set(nextDefaultSection.heading, bucket);
1076
+ customBeforeDefault.set(nextDefaultHeading, bucket);
949
1077
  return;
950
1078
  }
951
1079
  trailingCustomSections.push(section);
@@ -955,7 +1083,7 @@ var mergeTruthDocTemplate = (existingTemplate, defaultTemplate) => {
955
1083
  section
956
1084
  ]);
957
1085
  return [
958
- existingParsed.preamble,
1086
+ stripManagedFrontmatterFields(existingParsed.preamble),
959
1087
  ...mergedSections.map((section) => section.block),
960
1088
  ...trailingCustomSections.map((section) => section.block),
961
1089
  ""
@@ -965,11 +1093,8 @@ var renderBehaviorDocTemplateFile = () => {
965
1093
  return [
966
1094
  "---",
967
1095
  "status: active",
968
- "doc_type: behavior",
969
- "truth_kind: behavior",
1096
+ "truth_kind: engineering-behavior",
970
1097
  `last_reviewed: ${currentDate()}`,
971
- "source_of_truth:",
972
- " - {{source_of_truth}}",
973
1098
  "---",
974
1099
  "",
975
1100
  "# {{title}}",
@@ -979,7 +1104,7 @@ var renderBehaviorDocTemplateFile = () => {
979
1104
  "<!--",
980
1105
  "State the user/system outcome this behavior protects and why it exists.",
981
1106
  "Include the problem boundary and durable value; exclude roadmap, implementation plan, and historical narrative.",
982
- "List the code, config, docs, or tests that support the claim in source_of_truth rather than prose-only assertion.",
1107
+ "List the code, config, docs, or tests that support the claim in Source References rather than prose-only assertion.",
983
1108
  "-->",
984
1109
  "",
985
1110
  "{{purpose}}",
@@ -995,17 +1120,17 @@ var renderBehaviorDocTemplateFile = () => {
995
1120
  "",
996
1121
  "{{scope}}",
997
1122
  "",
998
- "This doc was created from the editable behavior-doc template at {{template_path}}.",
1123
+ "This doc was created from the editable engineering-behavior template at {{template_path}}.",
999
1124
  "",
1000
- "## Current Behavior",
1125
+ "## Current Implementation Behavior",
1001
1126
  "",
1002
1127
  "<!--",
1003
1128
  "Describe only current implemented behavior in present tense.",
1004
1129
  "Cover observable behavior, important defaults, and user/system-visible effects; exclude desired future behavior and speculative design.",
1005
- "Every non-obvious claim should be checkable from source_of_truth evidence.",
1130
+ "Every non-obvious claim should be checkable from Source References.",
1006
1131
  "-->",
1007
1132
  "",
1008
- "{{current_behavior}}",
1133
+ "{{current_implementation_behavior}}",
1009
1134
  "",
1010
1135
  "## Core Rules",
1011
1136
  "",
@@ -1034,14 +1159,23 @@ var renderBehaviorDocTemplateFile = () => {
1034
1159
  "",
1035
1160
  "{{contracts}}",
1036
1161
  "",
1037
- "## Product Decisions",
1162
+ "## Product Truth Links",
1163
+ "",
1164
+ "<!--",
1165
+ "List product truth docs this engineering doc realizes; author canonical realizes links in route YAML, not doc frontmatter.",
1166
+ "Use 'None.' when this is purely internal engineering behavior.",
1167
+ "-->",
1168
+ "",
1169
+ "{{product_truth_links}}",
1170
+ "",
1171
+ "## Engineering Decisions",
1038
1172
  "",
1039
1173
  "<!--",
1040
1174
  "Keep active decisions only, dated inline when added or changed.",
1041
1175
  "Explain decisions that shape behavior, boundaries, rejected alternatives, or migration constraints; replace stale decisions instead of appending historical logs.",
1042
1176
  "-->",
1043
1177
  "",
1044
- "{{decision}}",
1178
+ "{{engineering_decisions}}",
1045
1179
  "",
1046
1180
  "## Rationale",
1047
1181
  "",
@@ -1069,6 +1203,14 @@ var renderBehaviorDocTemplateFile = () => {
1069
1203
  "-->",
1070
1204
  "",
1071
1205
  "{{maintenance_notes}}",
1206
+ "",
1207
+ "## Source References",
1208
+ "",
1209
+ "<!--",
1210
+ "List source files, tests, configs, generated templates, route files, or product instructions that support current claims.",
1211
+ "-->",
1212
+ "",
1213
+ "{{source_references}}",
1072
1214
  ""
1073
1215
  ].join("\n");
1074
1216
  };
@@ -1076,17 +1218,30 @@ var sectionSpec = (heading, guidance, placeholder = titleToPlaceholder(heading))
1076
1218
  var PURPOSE_SECTION = sectionSpec("## Purpose", [
1077
1219
  "State the software-engineering outcome this document protects and why the documented surface exists.",
1078
1220
  "Include durable value, impacted users/systems, and the problem boundary; exclude roadmap, implementation plans, and historical narrative.",
1079
- "Keep claims traceable to source_of_truth evidence rather than prose-only assertion."
1221
+ "Keep claims traceable to Source References rather than prose-only assertion."
1080
1222
  ]);
1081
1223
  var SCOPE_SECTION = sectionSpec("## Scope", [
1082
1224
  "Define the one coherent surface this document owns, including actors, entrypoints, owned state/data, and handoffs to neighboring truth docs.",
1083
1225
  "Call out important out-of-scope boundaries here or in Non-Goals; split the doc when it mixes distinct outcomes, lifecycles, contracts, or owners."
1084
1226
  ]);
1085
- var PRODUCT_DECISIONS_SECTION = sectionSpec("## Product Decisions", [
1086
- "Keep active decisions only, dated inline when added or changed.",
1087
- "Capture decisions that shape behavior, interfaces, boundaries, compatibility, risk acceptance, or migration constraints.",
1088
- "Replace stale decisions instead of appending historical logs."
1089
- ], "decision");
1227
+ var PRODUCT_DECISIONS_SECTION = sectionSpec(
1228
+ "## Product Decisions",
1229
+ [
1230
+ "Keep active decisions only, dated inline when added or changed.",
1231
+ "Capture decisions that shape behavior, interfaces, boundaries, compatibility, risk acceptance, or migration constraints.",
1232
+ "Replace stale decisions instead of appending historical logs."
1233
+ ],
1234
+ "decision"
1235
+ );
1236
+ var ENGINEERING_DECISIONS_SECTION = sectionSpec(
1237
+ "## Engineering Decisions",
1238
+ [
1239
+ "Keep active engineering, architecture, contract, workflow, or operational decisions only, dated inline when added or changed.",
1240
+ "Do not restate product promises, product rationale, or business decisions here; link product truth instead.",
1241
+ "Replace stale decisions instead of appending historical logs."
1242
+ ],
1243
+ "engineering_decisions"
1244
+ );
1090
1245
  var RATIONALE_SECTION = sectionSpec("## Rationale", [
1091
1246
  "Explain why the current behavior, structure, or contract is this way, including tradeoffs and constraints.",
1092
1247
  "Tie rationale to evidence-backed facts and active decisions; do not use this as a changelog."
@@ -1099,15 +1254,19 @@ var MAINTENANCE_NOTES_SECTION = sectionSpec("## Maintenance Notes", [
1099
1254
  "List related tests, routing cautions, migration notes, compatibility risks, evidence drift risks, and review triggers for future maintainers or agents.",
1100
1255
  "Keep this operational and current-state focused, not historical."
1101
1256
  ]);
1102
- var renderTypedTruthDocTemplate = (truthKind, docType, title, sections) => {
1257
+ var SOURCE_REFERENCES_SECTION = sectionSpec(
1258
+ "## Source References",
1259
+ [
1260
+ "List source files, tests, configs, generated templates, route files, or product instructions that support current claims."
1261
+ ],
1262
+ "source_references"
1263
+ );
1264
+ var renderTypedTruthDocTemplate = (truthKind, title, sections) => {
1103
1265
  return [
1104
1266
  "---",
1105
1267
  "status: active",
1106
- `doc_type: ${docType}`,
1107
1268
  `truth_kind: ${truthKind}`,
1108
1269
  `last_reviewed: ${currentDate()}`,
1109
- "source_of_truth:",
1110
- " - {{source_of_truth}}",
1111
1270
  "---",
1112
1271
  "",
1113
1272
  `# ${title}`,
@@ -1115,14 +1274,77 @@ var renderTypedTruthDocTemplate = (truthKind, docType, title, sections) => {
1115
1274
  ...renderTemplateSection(PURPOSE_SECTION),
1116
1275
  ...renderTemplateSection(SCOPE_SECTION),
1117
1276
  ...sections.flatMap(renderTemplateSection),
1118
- ...renderTemplateSection(PRODUCT_DECISIONS_SECTION),
1277
+ ...renderTemplateSection(ENGINEERING_DECISIONS_SECTION),
1119
1278
  ...renderTemplateSection(RATIONALE_SECTION),
1120
1279
  ...renderTemplateSection(NON_GOALS_SECTION),
1121
- ...renderTemplateSection(MAINTENANCE_NOTES_SECTION)
1280
+ ...renderTemplateSection(MAINTENANCE_NOTES_SECTION),
1281
+ ...renderTemplateSection(SOURCE_REFERENCES_SECTION)
1282
+ ].join("\n");
1283
+ };
1284
+ var CORE_LANE_INVARIANT = "Do not make product docs a summary of engineering docs. Do not make engineering docs a detailed version of product docs. Product truth says what must be true and why. Engineering truth says how the repository currently realizes it.";
1285
+ var renderProductTruthDocTemplate = (truthKind, title, sections, includeNonGoals) => {
1286
+ return [
1287
+ "---",
1288
+ "status: active",
1289
+ `truth_kind: ${truthKind}`,
1290
+ `last_reviewed: ${currentDate()}`,
1291
+ "---",
1292
+ "",
1293
+ `# ${title}`,
1294
+ "",
1295
+ "<!--",
1296
+ CORE_LANE_INVARIANT,
1297
+ "Product docs may cite code directly when code proves current product behavior, but keep implementation flow, renderer internals, CLI envelopes, and generated file inventories in engineering truth.",
1298
+ "-->",
1299
+ "",
1300
+ ...sections.flatMap(renderTemplateSection),
1301
+ ...renderTemplateSection(PRODUCT_DECISIONS_SECTION),
1302
+ ...renderTemplateSection(
1303
+ sectionSpec(
1304
+ "## Engineering Realization Links",
1305
+ [
1306
+ "List engineering truth that realizes this product truth; author canonical realized_by links in route YAML, not doc frontmatter.",
1307
+ "Do not summarize those engineering docs."
1308
+ ],
1309
+ "engineering_realization_links"
1310
+ )
1311
+ ),
1312
+ ...includeNonGoals ? renderTemplateSection(NON_GOALS_SECTION) : [],
1313
+ ...renderTemplateSection(SOURCE_REFERENCES_SECTION)
1122
1314
  ].join("\n");
1123
1315
  };
1316
+ var renderProductCapabilityDocTemplateFile = () => {
1317
+ return renderProductTruthDocTemplate(
1318
+ "product-capability",
1319
+ "{{title}}",
1320
+ [
1321
+ sectionSpec("## Capability Promise", [
1322
+ "State the single user-visible capability and what must be true for users or stakeholders.",
1323
+ "Do not describe implementation mechanics here."
1324
+ ]),
1325
+ sectionSpec("## Users And Value", [
1326
+ "Describe who benefits from the capability and the durable value it protects.",
1327
+ "Tie claims to repository evidence, explicit user instruction, or current behavior."
1328
+ ]),
1329
+ sectionSpec("## Capability Scope", [
1330
+ "Define what this capability includes and excludes, including product boundary constraints and adjacent systems.",
1331
+ "Capture important scope limits, ownership boundaries, and non-goal pointers here; keep technical contracts in engineering truth."
1332
+ ]),
1333
+ sectionSpec("## Current Product Behavior", [
1334
+ "Describe current implemented user-visible behavior in present tense.",
1335
+ "Code files may appear in Source References when they directly prove current behavior."
1336
+ ]),
1337
+ sectionSpec("## Acceptance Criteria", [
1338
+ "List observable criteria that show the capability promise is currently satisfied.",
1339
+ "Include criteria that review whether the capability stays within its stated scope and boundary.",
1340
+ "Use criteria that can be reviewed from repository evidence or explicit product instruction."
1341
+ ])
1342
+ ],
1343
+ true
1344
+ );
1345
+ };
1124
1346
  var renderContractDocTemplateFile = () => {
1125
- return renderTypedTruthDocTemplate("contract", "contract", "{{title}}", [
1347
+ return renderTypedTruthDocTemplate("engineering-contract", "{{title}}", [
1126
1348
  sectionSpec("## Contract Surface", [
1127
1349
  "Identify the owned API, CLI, file format, event, protocol, permission boundary, or integration surface.",
1128
1350
  "State consumers/producers, stability level, and the source files/tests that define the contract."
@@ -1150,7 +1372,7 @@ var renderContractDocTemplateFile = () => {
1150
1372
  ]);
1151
1373
  };
1152
1374
  var renderArchitectureDocTemplateFile = () => {
1153
- return renderTypedTruthDocTemplate("architecture", "architecture", "{{title}}", [
1375
+ return renderTypedTruthDocTemplate("engineering-architecture", "{{title}}", [
1154
1376
  sectionSpec("## System Role", [
1155
1377
  "Describe the current architectural role of this subsystem/component in the larger system.",
1156
1378
  "State the primary responsibilities, consumers, providers, and why this boundary exists now."
@@ -1178,7 +1400,7 @@ var renderArchitectureDocTemplateFile = () => {
1178
1400
  ]);
1179
1401
  };
1180
1402
  var renderWorkflowDocTemplateFile = () => {
1181
- return renderTypedTruthDocTemplate("workflow", "behavior", "{{title}}", [
1403
+ return renderTypedTruthDocTemplate("engineering-workflow", "{{title}}", [
1182
1404
  sectionSpec("## Triggers", [
1183
1405
  "List events, commands, schedules, user actions, webhooks, or dependency signals that start this workflow.",
1184
1406
  "Include preconditions, authorization requirements, debounce/coalescing behavior, and disabled states when applicable."
@@ -1206,7 +1428,7 @@ var renderWorkflowDocTemplateFile = () => {
1206
1428
  ]);
1207
1429
  };
1208
1430
  var renderOperationsDocTemplateFile = () => {
1209
- return renderTypedTruthDocTemplate("operations", "behavior", "{{title}}", [
1431
+ return renderTypedTruthDocTemplate("engineering-operations", "{{title}}", [
1210
1432
  sectionSpec("## Operational Surface", [
1211
1433
  "Describe what operators, maintainers, or automated systems can observe or control for this surface.",
1212
1434
  "Include commands, dashboards, alerts, runbooks, jobs, or operational APIs that define current operations."
@@ -1234,7 +1456,7 @@ var renderOperationsDocTemplateFile = () => {
1234
1456
  ]);
1235
1457
  };
1236
1458
  var renderTestBehaviorDocTemplateFile = () => {
1237
- return renderTypedTruthDocTemplate("test-behavior", "behavior", "{{title}}", [
1459
+ return renderTypedTruthDocTemplate("engineering-test-behavior", "{{title}}", [
1238
1460
  sectionSpec("## Test Surface", [
1239
1461
  "Define the behavior, contract, architecture, or workflow surface these tests verify.",
1240
1462
  "Link the canonical truth docs and code paths the tests are meant to protect."
@@ -1279,18 +1501,19 @@ var renderBehaviorLeafDocTemplate = (config, template = renderBehaviorDocTemplat
1279
1501
  area: defaultArea,
1280
1502
  contracts: "- External contracts should link to the nearest canonical contract doc when one exists.",
1281
1503
  core_rules: "- Truth README files are indexes; behavior truth belongs in bounded leaf docs.",
1282
- current_behavior: "- Document current behavior here when implementation changes make repository truth incomplete.",
1283
- decision: `- Decision (${today}): 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.`,
1284
1506
  flows_and_states: "- None beyond current behavior.",
1285
1507
  maintenance_notes: "- Update this doc when routed implementation changes alter current behavior, rules, contracts, or decisions.",
1286
1508
  non_goals: "- This doc is not a catch-all for unrelated repository behavior.",
1287
1509
  purpose: `Describe why the default ${title.toLowerCase()} behavior surface exists and what outcome it protects.`,
1288
1510
  rationale: "Bounded leaf docs keep agent context focused and prevent large products from accumulating unreviewable feature manuals.",
1511
+ product_truth_links: "- None.",
1289
1512
  scope: `This bounded leaf truth doc owns the default ${title.toLowerCase()} behavior surface created by Truthmark.`,
1290
- source_of_truth: sourceOfTruth,
1513
+ source_references: `- ${sourceOfTruth}`,
1291
1514
  template_path: BEHAVIOR_DOC_TEMPLATE_PATH,
1292
1515
  title: `${title} Overview`,
1293
- truth_kind: "behavior"
1516
+ truth_kind: "engineering-behavior"
1294
1517
  });
1295
1518
  };
1296
1519
 
@@ -1399,8 +1622,7 @@ var isUnsafeRepoRelativePath = (value) => {
1399
1622
  var joinWorkspacePath = (workspace, childPath) => {
1400
1623
  return normalizeRepoRelativePath(`${workspace}/${childPath}`);
1401
1624
  };
1402
- var portalOutputFor = (workspace) => joinWorkspacePath(workspace, "generated/portal");
1403
- var portalTemplateFor = (templatesRoot) => joinWorkspacePath(templatesRoot, "portal.html");
1625
+ var portalOutputFor = (workspace) => joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.portalOutput);
1404
1626
  var pathsOverlap = (left, right) => {
1405
1627
  const normalizedLeft = normalizeRepoRelativePath(left);
1406
1628
  const normalizedRight = normalizeRepoRelativePath(right);
@@ -1444,12 +1666,6 @@ var unsupportedShapeDiagnostics = (parsedConfig, configPath) => {
1444
1666
  var validateWorkspacePaths = (rawConfig, configPath) => {
1445
1667
  const diagnostics = [];
1446
1668
  const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
1447
- const childPaths = [
1448
- ["truthmark.routes.index", rawConfig.truthmark.routes.index],
1449
- ["truthmark.routes.areas", rawConfig.truthmark.routes.areas],
1450
- ["truthmark.truth.root", rawConfig.truthmark.truth.root],
1451
- ["truthmark.templates.root", rawConfig.truthmark.templates.root]
1452
- ];
1453
1669
  if (isUnsafeRepoRelativePath(rawConfig.truthmark.workspace) || FORBIDDEN_WORKSPACE_OVERLAPS.some((forbidden) => pathsOverlap(workspace, forbidden))) {
1454
1670
  diagnostics.push(
1455
1671
  toConfigDiagnostic(
@@ -1458,33 +1674,6 @@ var validateWorkspacePaths = (rawConfig, configPath) => {
1458
1674
  )
1459
1675
  );
1460
1676
  }
1461
- for (const [name, value] of childPaths) {
1462
- if (isUnsafeRepoRelativePath(value)) {
1463
- diagnostics.push(
1464
- toConfigDiagnostic(
1465
- `${name} must be a non-empty path relative to truthmark.workspace without absolute or parent traversal segments.`,
1466
- configPath
1467
- )
1468
- );
1469
- }
1470
- }
1471
- const portalOutput = normalizeRepoRelativePath("generated/portal");
1472
- const controlledWorkspaceChildren = [
1473
- ["truthmark.routes.index", rawConfig.truthmark.routes.index],
1474
- ["truthmark.routes.areas", rawConfig.truthmark.routes.areas],
1475
- ["truthmark.truth.root", rawConfig.truthmark.truth.root],
1476
- ["truthmark.templates.root", rawConfig.truthmark.templates.root]
1477
- ];
1478
- for (const [name, value] of controlledWorkspaceChildren) {
1479
- if (pathsOverlap(portalOutput, value)) {
1480
- diagnostics.push(
1481
- toConfigDiagnostic(
1482
- `Truthmark Portal output ${portalOutputFor(workspace)} must not overlap ${name}; Portal output is generated and must stay outside controlled truth, routing, and template paths.`,
1483
- configPath
1484
- )
1485
- );
1486
- }
1487
- }
1488
1677
  for (const target of rawConfig.instruction_targets ?? DEFAULT_INSTRUCTION_TARGETS) {
1489
1678
  if (isUnsafeRepoRelativePath(target) || pathsOverlap(workspace, target)) {
1490
1679
  diagnostics.push(
@@ -1499,28 +1688,33 @@ var validateWorkspacePaths = (rawConfig, configPath) => {
1499
1688
  };
1500
1689
  var normalizeConfig = (rawConfig) => {
1501
1690
  const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
1502
- const routesIndex = joinWorkspacePath(workspace, rawConfig.truthmark.routes.index);
1503
- const routeAreasRoot = joinWorkspacePath(workspace, rawConfig.truthmark.routes.areas);
1504
- const truthRoot3 = joinWorkspacePath(workspace, rawConfig.truthmark.truth.root);
1505
- const templatesRoot = joinWorkspacePath(workspace, rawConfig.truthmark.templates.root);
1691
+ const routesIndex = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.routesIndex);
1692
+ const routeAreasRoot = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.routeAreasRoot);
1693
+ const productTruthRoot = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.productTruthRoot);
1694
+ const engineeringTruthRoot = joinWorkspacePath(
1695
+ workspace,
1696
+ DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
1697
+ );
1698
+ const templatesRoot = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.templatesRoot);
1506
1699
  const portalOutput = portalOutputFor(workspace);
1507
- const portalTemplate = portalTemplateFor(templatesRoot);
1700
+ const portalTemplate = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.portalTemplate);
1508
1701
  return {
1509
1702
  version: rawConfig.version,
1510
1703
  platforms: rawConfig.platforms ?? [...DEFAULT_PLATFORMS],
1511
1704
  truthmark: {
1512
1705
  workspace,
1513
1706
  routes: {
1514
- index: normalizeRepoRelativePath(rawConfig.truthmark.routes.index),
1515
- areas: normalizeRepoRelativePath(rawConfig.truthmark.routes.areas),
1516
- defaultArea: rawConfig.truthmark.routes.default_area,
1517
- maxDelegationDepth: rawConfig.truthmark.routes.max_delegation_depth
1707
+ index: DERIVED_TRUTHMARK_PATHS.routesIndex,
1708
+ areas: DERIVED_TRUTHMARK_PATHS.routeAreasRoot,
1709
+ defaultArea: DERIVED_TRUTHMARK_PATHS.defaultArea,
1710
+ maxDelegationDepth: DERIVED_TRUTHMARK_PATHS.maxDelegationDepth
1518
1711
  },
1519
1712
  truth: {
1520
- root: normalizeRepoRelativePath(rawConfig.truthmark.truth.root)
1713
+ productRoot: DERIVED_TRUTHMARK_PATHS.productTruthRoot,
1714
+ engineeringRoot: DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
1521
1715
  },
1522
1716
  templates: {
1523
- root: normalizeRepoRelativePath(rawConfig.truthmark.templates.root)
1717
+ root: DERIVED_TRUTHMARK_PATHS.templatesRoot
1524
1718
  },
1525
1719
  generated: {
1526
1720
  portal: {
@@ -1530,7 +1724,8 @@ var normalizeConfig = (rawConfig) => {
1530
1724
  paths: {
1531
1725
  routesIndex,
1532
1726
  routeAreasRoot,
1533
- truthRoot: truthRoot3,
1727
+ productTruthRoot,
1728
+ engineeringTruthRoot,
1534
1729
  templatesRoot,
1535
1730
  portalOutput,
1536
1731
  portalTemplate
@@ -1538,7 +1733,8 @@ var normalizeConfig = (rawConfig) => {
1538
1733
  controlledPaths: [
1539
1734
  routesIndex,
1540
1735
  `${routeAreasRoot}/**/*.md`,
1541
- `${truthRoot3}/**/*.md`,
1736
+ `${productTruthRoot}/**/*.md`,
1737
+ `${engineeringTruthRoot}/**/*.md`,
1542
1738
  `${templatesRoot}/*.md`
1543
1739
  ]
1544
1740
  },
@@ -1624,8 +1820,18 @@ var loadConfig = async (rootDir) => {
1624
1820
  // src/init/hierarchy.ts
1625
1821
  import fs5 from "fs/promises";
1626
1822
  var truthRoot2 = resolveTruthDocsRoot;
1823
+ var BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-behavior.md";
1824
+ var CONTRACT_DOC_TEMPLATE_FILE_NAME = "engineering-contract.md";
1825
+ var ARCHITECTURE_DOC_TEMPLATE_FILE_NAME = "engineering-architecture.md";
1826
+ var WORKFLOW_DOC_TEMPLATE_FILE_NAME = "engineering-workflow.md";
1827
+ var OPERATIONS_DOC_TEMPLATE_FILE_NAME = "engineering-operations.md";
1828
+ var TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-test-behavior.md";
1829
+ var PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME = "product-capability.md";
1627
1830
  var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePath) => {
1628
- const rootIndexSource = await fs5.readFile(resolveRepoPath(rootDir, rootIndexPath), "utf8");
1831
+ const rootIndexSource = await fs5.readFile(
1832
+ resolveRepoPath(rootDir, rootIndexPath),
1833
+ "utf8"
1834
+ );
1629
1835
  const parsedRootIndex = parseAreasMarkdown(rootIndexSource);
1630
1836
  return parsedRootIndex.areaFileReferences.some(
1631
1837
  (areaReference) => areaReference.areaFiles.includes(childRoutePath)
@@ -1637,7 +1843,10 @@ var truthTemplatePath = (config, fileName) => {
1637
1843
  var readBehaviorDocTemplate = async (rootDir, config) => {
1638
1844
  try {
1639
1845
  return await fs5.readFile(
1640
- resolveRepoPath(rootDir, truthTemplatePath(config, "behavior-doc.md")),
1846
+ resolveRepoPath(
1847
+ rootDir,
1848
+ truthTemplatePath(config, BEHAVIOR_DOC_TEMPLATE_FILE_NAME)
1849
+ ),
1641
1850
  "utf8"
1642
1851
  );
1643
1852
  } catch (error) {
@@ -1648,12 +1857,22 @@ var readBehaviorDocTemplate = async (rootDir, config) => {
1648
1857
  }
1649
1858
  };
1650
1859
  var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTemplate) => {
1651
- const seededResult = await ensureRepoFile(rootDir, templatePath, defaultTemplate);
1860
+ const seededResult = await ensureRepoFile(
1861
+ rootDir,
1862
+ templatePath,
1863
+ defaultTemplate
1864
+ );
1652
1865
  if (seededResult.status !== "unchanged") {
1653
1866
  return seededResult;
1654
1867
  }
1655
- const existingTemplate = await fs5.readFile(resolveRepoPath(rootDir, templatePath), "utf8");
1656
- const mergedTemplate = mergeTruthDocTemplate(existingTemplate, defaultTemplate);
1868
+ const existingTemplate = await fs5.readFile(
1869
+ resolveRepoPath(rootDir, templatePath),
1870
+ "utf8"
1871
+ );
1872
+ const mergedTemplate = mergeTruthDocTemplate(
1873
+ existingTemplate,
1874
+ defaultTemplate
1875
+ );
1657
1876
  return writeRepoFile(rootDir, templatePath, mergedTemplate);
1658
1877
  };
1659
1878
  var scaffoldHierarchy = async (rootDir, config) => {
@@ -1668,14 +1887,31 @@ var scaffoldHierarchy = async (rootDir, config) => {
1668
1887
  renderHierarchicalAreasIndexTemplate(config)
1669
1888
  )
1670
1889
  );
1671
- if (await rootIndexReferencesChildRoute(rootDir, config.truthmark.paths.routesIndex, childRoutePath)) {
1672
- results.push(await ensureRepoFile(rootDir, childRoutePath, renderChildAreaTemplate(config)));
1890
+ if (await rootIndexReferencesChildRoute(
1891
+ rootDir,
1892
+ config.truthmark.paths.routesIndex,
1893
+ childRoutePath
1894
+ )) {
1895
+ results.push(
1896
+ await ensureRepoFile(
1897
+ rootDir,
1898
+ childRoutePath,
1899
+ renderChildAreaTemplate(config)
1900
+ )
1901
+ );
1673
1902
  }
1674
1903
  results.push(
1675
1904
  await ensureRepoFile(
1676
1905
  rootDir,
1677
- `${truthDocsRoot}/README.md`,
1678
- renderTruthRootReadmeTemplate(config)
1906
+ `${resolveEngineeringTruthRoot(config)}/README.md`,
1907
+ renderTruthRootReadmeTemplate(config, "engineering")
1908
+ )
1909
+ );
1910
+ results.push(
1911
+ await ensureRepoFile(
1912
+ rootDir,
1913
+ `${config.truthmark.paths.productTruthRoot}/README.md`,
1914
+ renderTruthRootReadmeTemplate(config, "product")
1679
1915
  )
1680
1916
  );
1681
1917
  results.push(
@@ -1688,45 +1924,52 @@ var scaffoldHierarchy = async (rootDir, config) => {
1688
1924
  results.push(
1689
1925
  await ensureOrUpdateTruthDocTemplate(
1690
1926
  rootDir,
1691
- truthTemplatePath(config, "behavior-doc.md"),
1927
+ truthTemplatePath(config, BEHAVIOR_DOC_TEMPLATE_FILE_NAME),
1692
1928
  renderBehaviorDocTemplateFile()
1693
1929
  )
1694
1930
  );
1695
1931
  results.push(
1696
1932
  await ensureOrUpdateTruthDocTemplate(
1697
1933
  rootDir,
1698
- truthTemplatePath(config, "contract-doc.md"),
1934
+ truthTemplatePath(config, CONTRACT_DOC_TEMPLATE_FILE_NAME),
1699
1935
  renderContractDocTemplateFile()
1700
1936
  )
1701
1937
  );
1702
1938
  results.push(
1703
1939
  await ensureOrUpdateTruthDocTemplate(
1704
1940
  rootDir,
1705
- truthTemplatePath(config, "architecture-doc.md"),
1941
+ truthTemplatePath(config, ARCHITECTURE_DOC_TEMPLATE_FILE_NAME),
1706
1942
  renderArchitectureDocTemplateFile()
1707
1943
  )
1708
1944
  );
1709
1945
  results.push(
1710
1946
  await ensureOrUpdateTruthDocTemplate(
1711
1947
  rootDir,
1712
- truthTemplatePath(config, "workflow-doc.md"),
1948
+ truthTemplatePath(config, WORKFLOW_DOC_TEMPLATE_FILE_NAME),
1713
1949
  renderWorkflowDocTemplateFile()
1714
1950
  )
1715
1951
  );
1716
1952
  results.push(
1717
1953
  await ensureOrUpdateTruthDocTemplate(
1718
1954
  rootDir,
1719
- truthTemplatePath(config, "operations-doc.md"),
1955
+ truthTemplatePath(config, OPERATIONS_DOC_TEMPLATE_FILE_NAME),
1720
1956
  renderOperationsDocTemplateFile()
1721
1957
  )
1722
1958
  );
1723
1959
  results.push(
1724
1960
  await ensureOrUpdateTruthDocTemplate(
1725
1961
  rootDir,
1726
- truthTemplatePath(config, "test-behavior-doc.md"),
1962
+ truthTemplatePath(config, TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME),
1727
1963
  renderTestBehaviorDocTemplateFile()
1728
1964
  )
1729
1965
  );
1966
+ results.push(
1967
+ await ensureOrUpdateTruthDocTemplate(
1968
+ rootDir,
1969
+ truthTemplatePath(config, PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME),
1970
+ renderProductCapabilityDocTemplateFile()
1971
+ )
1972
+ );
1730
1973
  const behaviorDocTemplate = await readBehaviorDocTemplate(rootDir, config);
1731
1974
  results.push(
1732
1975
  await ensureRepoFile(
@@ -1766,26 +2009,66 @@ var renderAuditEvidenceCheckedSection = (items) => {
1766
2009
  };
1767
2010
 
1768
2011
  // src/agents/shared.ts
2012
+ var renderBulletLine = (line) => {
2013
+ const normalized = line.trim().replace(/^-\s*/u, "");
2014
+ return `- ${normalized}`;
2015
+ };
2016
+ var renderBulletBlock = (lines, indent = " ") => {
2017
+ return lines.split(/\n/u).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => `${indent}${renderBulletLine(line)}`).join("\n");
2018
+ };
2019
+ var renderLaneClassificationRuleBlock = (config = defaultAgentConfig(), indent = " ") => {
2020
+ const [, ...rules] = renderLaneClassificationInstructions(config).split(/\n/u);
2021
+ return renderBulletBlock(rules.join("\n"), indent);
2022
+ };
2023
+ var renderReadOnlyLaneClassificationRuleBlock = (config = defaultAgentConfig(), indent = " ") => {
2024
+ const productTruthRoot = resolveProductTruthRoot(config);
2025
+ const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2026
+ return renderBulletBlock(
2027
+ [
2028
+ "classify the request or changed surface as product-lane, engineering-lane, both-lane, or ambiguous for reporting only",
2029
+ `product-lane ownership belongs under ${productTruthRoot} and describes product promises, boundaries, rationale, decisions, and success criteria`,
2030
+ `engineering-lane ownership belongs under ${engineeringTruthRoot} and describes source-backed current realization, contracts, architecture, workflows, operations, or tests`,
2031
+ "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",
2033
+ LANE_INVARIANT
2034
+ ].join("\n"),
2035
+ indent
2036
+ );
2037
+ };
1769
2038
  var DECISION_TRUTH_INSTRUCTIONS = [
1770
2039
  "Decision truth lives in the canonical doc it governs; date active decisions inline when added or changed.",
1771
2040
  "Do not create separate active-decision ADR/planning logs; replace the active decision and let Git history carry the audit trail.",
1772
- "Update Product Decisions and Rationale when a decision changes behavior."
2041
+ "Product decisions belong in product truth; engineering, architecture, contract, workflow, and operational decisions belong in engineering truth."
1773
2042
  ].join("\n");
2043
+ var LANE_INVARIANT = "Do not make product docs a summary of engineering docs. Do not make engineering docs a detailed version of product docs. Product truth says what must be true and why. Engineering truth says how the repository currently realizes it.";
2044
+ var renderLaneClassificationInstructions = (config = defaultAgentConfig()) => {
2045
+ const productTruthRoot = resolveProductTruthRoot(config);
2046
+ const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2047
+ return [
2048
+ "Lane classification gate:",
2049
+ "- before writing canonical truth docs, classify the request or change as product-lane, engineering-lane, both-lane, or ambiguous",
2050
+ `- product-lane writes belong under ${productTruthRoot} and state product promises, boundaries, rationale, decisions, and success criteria`,
2051
+ `- engineering-lane writes belong under ${engineeringTruthRoot} and state source-backed current realization, contracts, architecture, workflows, operations, or tests`,
2052
+ "- 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",
2054
+ `- ${LANE_INVARIANT}`
2055
+ ].join("\n");
2056
+ };
1774
2057
  var EVIDENCE_AUTHORITY_INSTRUCTIONS = [
1775
2058
  "Repository instruction files and explicitly configured policy docs remain instruction authority when present; do not assume a repository uses any particular policy path.",
1776
2059
  "Implementation code and canonical truth docs are inspected evidence for current behavior; they do not silently override workflow write boundaries."
1777
2060
  ].join("\n");
1778
2061
  var REPOSITORY_INTELLIGENCE_INSTRUCTIONS = [
1779
- "Repository intelligence artifacts are optional derived context: RepoIndex, RouteMap, ImpactSet, and ContextPack may guide routing, context selection, and verification planning when available.",
2062
+ "Repository intelligence artifacts are optional derived context: RepoIndex, RouteMap, ImpactSet, and WorkflowState/action context may guide routing, write boundaries, and verification planning when available.",
1780
2063
  "They do not override checkout evidence, canonical truth docs, route files, or workflow write boundaries.",
1781
2064
  "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."
1782
2065
  ].join("\n");
1783
2066
  var FEATURE_DOC_TEMPLATE_INSTRUCTIONS = [
1784
2067
  "When creating or updating a truth doc, inspect the routed truth kind and use the matching template under the configured Truthmark templates root.",
1785
- "Supported kinds: behavior, contract, architecture, workflow, operations, and test-behavior.",
2068
+ "Supported kinds: product-capability, engineering-behavior, engineering-contract, engineering-architecture, engineering-workflow, engineering-operations, and engineering-test-behavior.",
1786
2069
  "Treat the HTML comments under each template section as normative authoring guidance for that section.",
1787
2070
  "Align existing docs to that template and write or repair section content so it satisfies the comment guidance while preserving accurate authored content.",
1788
- "If the template is missing, use Scope, Product Decisions, Rationale, and the kind-specific current-truth section.",
2071
+ "If the template is missing, use lane-specific sections: product truth says what must be true and why; engineering truth says how the repository currently realizes it.",
1789
2072
  "Teams may edit template files under the configured Truthmark templates root to define their local truth-doc standards."
1790
2073
  ].join("\n");
1791
2074
  var renderTruthDocOwnershipGateSection = (subject, outcome) => {
@@ -1798,12 +2081,12 @@ var renderTruthDocOwnershipGateSection = (subject, outcome) => {
1798
2081
  ].join("\n");
1799
2082
  };
1800
2083
  var TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS = [
1801
- "Product Decisions/Rationale preservation gate:",
1802
- "- before any truth-doc split, restructure, or shape repair, inventory existing Product Decisions and Rationale sections in every source or touched truth doc",
1803
- "- preserve each current decision and rationale in the bounded owner doc it governs; when splitting, move it to the new owner doc rather than deleting it or leaving it in an index",
2084
+ "Decision/Rationale preservation gate:",
2085
+ "- 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
+ "- 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",
1804
2087
  "- 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",
1805
2088
  "- if ownership of a decision or rationale is unclear, block with manual-review files instead of deleting it or guessing",
1806
- "- after the edit, verify every touched truth doc still has Product Decisions and Rationale sections and every pre-existing entry is preserved, moved, narrowed, removed with evidence, or blocked"
2089
+ "- 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"
1807
2090
  ].join("\n");
1808
2091
  var renderTruthDocRestructureGateSection = (scope) => {
1809
2092
  return [
@@ -1824,7 +2107,7 @@ var renderRouteFirstEvidenceGateSection = (subject, noImpactedDocOutcome) => {
1824
2107
  return [
1825
2108
  "Evidence Gate:",
1826
2109
  `- route-first: map ${subject} to bounded route owners and primary canonical docs`,
1827
- "- review new or changed behavior-bearing claims only in touched docs, route ownership, Product Decisions, and Rationale",
2110
+ "- review new or changed behavior-bearing claims only in touched docs, route ownership, lane-specific decisions, and rationale",
1828
2111
  "- support claims with primary checkout evidence: implementation, config, routing, generated templates, schemas, or contract definitions",
1829
2112
  "- tests/examples/canonical docs corroborate; they are not sole proof when implementation conflicts",
1830
2113
  "- remove, narrow, or block unsupported claims",
@@ -1834,7 +2117,7 @@ var renderRouteFirstEvidenceGateSection = (subject, noImpactedDocOutcome) => {
1834
2117
  var renderTopologyEvidenceGateSection = () => {
1835
2118
  return [
1836
2119
  "Evidence Gate:",
1837
- "- apply the Evidence Gate before finishing when Truth Structure writes routed docs, ownership claims, Product Decisions, or Rationale",
2120
+ "- apply the Evidence Gate before finishing when Truth Structure writes routed docs, ownership claims, lane-specific decisions, or rationale",
1838
2121
  "- support ownership/behavior claims with topology or primary checkout evidence from layout, implementation boundaries, docs, config, route files, tests, templates, schemas, or contracts",
1839
2122
  "- tests/examples/canonical docs corroborate; remove, narrow, or block unsupported claims"
1840
2123
  ].join("\n");
@@ -1868,7 +2151,9 @@ var renderCodexSubagentModeSection = (agents, parentRule, writeAgents = []) => {
1868
2151
  };
1869
2152
  var renderOpenCodeSubagentModeSection = (agents, parentRule, writeAgents = []) => {
1870
2153
  const mentions = agents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
1871
- const writeMentions = writeAgents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
2154
+ const writeMentions = writeAgents.map(
2155
+ (agent) => `@${agent.replace(/_/gu, "-")}`
2156
+ );
1872
2157
  const writeAgentLines = writeMentions.length > 0 ? [
1873
2158
  `- dispatch write-capable project subagents only with explicit write leases: ${writeMentions.join(", ")}`,
1874
2159
  "- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
@@ -1888,7 +2173,9 @@ var renderOpenCodeSubagentModeSection = (agents, parentRule, writeAgents = []) =
1888
2173
  ].join("\n");
1889
2174
  };
1890
2175
  var renderClaudeSubagentModeSection = (agents, parentRule, writeAgents = []) => {
1891
- const mentions = agents.map((agent) => `${agent.replace(/_/gu, "-")} subagent`);
2176
+ const mentions = agents.map(
2177
+ (agent) => `${agent.replace(/_/gu, "-")} subagent`
2178
+ );
1892
2179
  const writeMentions = writeAgents.map(
1893
2180
  (agent) => `${agent.replace(/_/gu, "-")} subagent`
1894
2181
  );
@@ -1912,7 +2199,9 @@ var renderClaudeSubagentModeSection = (agents, parentRule, writeAgents = []) =>
1912
2199
  };
1913
2200
  var renderCopilotCustomAgentModeSection = (agents, parentRule, writeAgents = []) => {
1914
2201
  const mentions = agents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
1915
- const writeMentions = writeAgents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
2202
+ const writeMentions = writeAgents.map(
2203
+ (agent) => `@${agent.replace(/_/gu, "-")}`
2204
+ );
1916
2205
  const writeAgentLines = writeMentions.length > 0 ? [
1917
2206
  `- dispatch write-capable project custom agents only with explicit write leases: ${writeMentions.join(", ")}`,
1918
2207
  "- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
@@ -1933,7 +2222,9 @@ var renderCopilotCustomAgentModeSection = (agents, parentRule, writeAgents = [])
1933
2222
  };
1934
2223
  var renderGeminiSubagentModeSection = (agents, parentRule, writeAgents = []) => {
1935
2224
  const mentions = agents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
1936
- const writeMentions = writeAgents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
2225
+ const writeMentions = writeAgents.map(
2226
+ (agent) => `@${agent.replace(/_/gu, "-")}`
2227
+ );
1937
2228
  const writeAgentLines = writeMentions.length > 0 ? [
1938
2229
  `- dispatch write-capable project subagents only with explicit write leases: ${writeMentions.join(", ")}`,
1939
2230
  "- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
@@ -1956,13 +2247,15 @@ var defaultAgentConfig = () => {
1956
2247
  return createDefaultConfig();
1957
2248
  };
1958
2249
  var renderHierarchySummary = (config) => {
1959
- const truthRoot3 = resolveTruthDocsRoot(config);
2250
+ const productRoot = resolveProductTruthRoot(config);
2251
+ const engineeringRoot = resolveEngineeringTruthRoot(config);
1960
2252
  return [
1961
2253
  "Truthmark hierarchy hints:",
1962
2254
  "- Config, when present: .truthmark/config.yml",
1963
2255
  `- Root route index, when present: ${config.truthmark.paths.routesIndex}`,
1964
2256
  `- Area route files, when present: ${config.truthmark.paths.routeAreasRoot}/**/*.md`,
1965
- `- Truth docs, when present: ${truthRoot3}/**/*.md`
2257
+ `- Product truth docs, when present: ${productRoot}/**/*.md`,
2258
+ `- Engineering truth docs, when present: ${engineeringRoot}/**/*.md`
1966
2259
  ].join("\n");
1967
2260
  };
1968
2261
 
@@ -1977,8 +2270,12 @@ var TRUTHMARK_VERSION = packageJson.version;
1977
2270
  var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
1978
2271
  var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
1979
2272
  var renderCompactHierarchySummary = (config) => {
1980
- const truthRoot3 = resolveTruthDocsRoot(config);
1981
- return `Hierarchy hints: config .truthmark/config.yml when present; routes ${config.truthmark.paths.routesIndex} and ${config.truthmark.paths.routeAreasRoot}/**/*.md when present; Truth docs: ${truthRoot3}/**/*.md when present.`;
2273
+ const productTruthRoot = resolveProductTruthRoot(config);
2274
+ const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2275
+ const truthDocRoots = Array.from(/* @__PURE__ */ new Set([productTruthRoot, engineeringTruthRoot])).map(
2276
+ (truthRoot3) => `${truthRoot3}/**/*.md`
2277
+ );
2278
+ 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.`;
1982
2279
  };
1983
2280
  var renderAgentsBlock = (config = defaultAgentConfig()) => {
1984
2281
  const portalLine = config.truthmark.generated.portal.enabled ? `Truthmark Portal is a separate manual-only presentation workflow. Run it only when explicitly requested; it writes generated non-canonical static files under ${config.truthmark.paths.portalOutput}/. Markdown remains canonical.` : null;
@@ -2071,7 +2368,8 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2071
2368
  requiredGates: [
2072
2369
  "topology quality",
2073
2370
  "truth-doc ownership",
2074
- "Product Decisions/Rationale preservation",
2371
+ "lane classification",
2372
+ "Decision/Rationale preservation",
2075
2373
  "truth-doc shape repair when restructuring",
2076
2374
  "Evidence Gate"
2077
2375
  ],
@@ -2114,7 +2412,9 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2114
2412
  ],
2115
2413
  requiredGates: [
2116
2414
  "truth-doc ownership",
2117
- "Product Decisions/Rationale preservation",
2415
+ "lane classification",
2416
+ "Decision/Rationale preservation",
2417
+ "lane split and relationship repair",
2118
2418
  "truth-doc shape repair when restructuring",
2119
2419
  "Evidence Gate"
2120
2420
  ],
@@ -2155,7 +2455,8 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2155
2455
  ],
2156
2456
  requiredGates: [
2157
2457
  "truth-doc ownership",
2158
- "Product Decisions/Rationale preservation",
2458
+ "lane classification",
2459
+ "Decision/Rationale preservation",
2159
2460
  "Evidence Gate",
2160
2461
  "truth-doc shape repair when restructuring"
2161
2462
  ],
@@ -2191,10 +2492,10 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2191
2492
  "truth audit"
2192
2493
  ],
2193
2494
  forbiddenAdjacency: [
2194
- "must not edit truth docs",
2195
- "must not edit truth routing"
2495
+ "must not edit truth docs except through follow-up Truth Sync",
2496
+ "must not edit truth routing except through follow-up Truth Sync"
2196
2497
  ],
2197
- requiredGates: ["truth-doc ownership"],
2498
+ requiredGates: ["lane classification", "truth-doc ownership"],
2198
2499
  allowedWrites: ["functional code"],
2199
2500
  reportSections: ["Truth docs used", "Code updated", "Verification"]
2200
2501
  },
@@ -2326,9 +2627,6 @@ var getTruthmarkWorkflow = (id) => {
2326
2627
  };
2327
2628
 
2328
2629
  // src/agents/truth-check.ts
2329
- var renderMarkdownExample = (content) => {
2330
- return ["```md", content, "```"].join("\n");
2331
- };
2332
2630
  var TRUTH_CHECK_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-check; Codex /truthmark-check or $truthmark-check; Claude Code /truthmark-check; GitHub Copilot /truthmark-check; Gemini CLI /truthmark:check.";
2333
2631
  var renderTruthCheckReportExample = (config = defaultAgentConfig()) => {
2334
2632
  const rootRouteIndex = config.truthmark.paths.routesIndex;
@@ -2355,7 +2653,7 @@ ${renderAuditEvidenceCheckedSection([
2355
2653
  Validation:
2356
2654
  - truthmark check`;
2357
2655
  };
2358
- var renderTruthCheckSkillBody = (config = defaultAgentConfig(), options = {}) => {
2656
+ var renderTruthCheckProcedureBody = (config = defaultAgentConfig(), options = {}) => {
2359
2657
  const workflow = getTruthmarkWorkflow("truthmark-check");
2360
2658
  const claudeSubagentMode = options.includeClaudeSubagentMode ? `${renderClaudeSubagentModeSection(
2361
2659
  workflow.subagents ?? [],
@@ -2382,15 +2680,7 @@ var renderTruthCheckSkillBody = (config = defaultAgentConfig(), options = {}) =>
2382
2680
 
2383
2681
  ` : "";
2384
2682
  const subagentMode = `${claudeSubagentMode}${codexSubagentMode}${copilotCustomAgentMode}${openCodeSubagentMode}`;
2385
- return `---
2386
- name: truthmark-check
2387
- description: ${workflow.description}
2388
- argument-hint: Optional area, doc path, or audit focus
2389
- user-invocable: true
2390
- truthmark-version: ${TRUTHMARK_VERSION}
2391
- ---
2392
-
2393
- # Truthmark Check
2683
+ return `# Truthmark Check
2394
2684
 
2395
2685
  Use this skill to audit repository truth health.
2396
2686
 
@@ -2399,12 +2689,25 @@ Invocations: ${TRUTH_CHECK_EXPLICIT_INVOCATIONS}
2399
2689
  Truth Check is agent-led:
2400
2690
 
2401
2691
  - inspect .truthmark/config.yml and configured route files only when they exist; then inspect canonical docs and relevant implementation directly
2402
- - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2403
2692
  - inspect the configured root route index at ${config.truthmark.paths.routesIndex} and relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ when they exist
2693
+ - Evidence authority:
2694
+ ${renderBulletBlock(EVIDENCE_AUTHORITY_INSTRUCTIONS)}
2695
+ - Lane classification:
2696
+ ${renderReadOnlyLaneClassificationRuleBlock(config)}
2404
2697
  - check that current docs describe current code rather than historical plans
2698
+ - keep lane and cross-lane checks route-first and bounded:
2699
+ - for a narrow audit, inspect only the routed area and directly linked counterpart docs
2700
+ - for root-wide truth health, first build a cheap route-map/index from route files, then inspect only mismatches and linked leaves
2701
+ - inspect product counterparts for engineering docs only when route YAML claims a product relationship, or when the user explicitly asks for user-visible product coverage
2702
+ - check lane root/kind alignment for product truth under ${config.truthmark.paths.productTruthRoot} and engineering truth under ${config.truthmark.paths.engineeringTruthRoot}
2703
+ - check route YAML cross-lane realized_by and realizes links for existence and lane compatibility
2704
+ - report missing product links for user-visible engineering docs only as a second-pass review diagnostic, not as default full-document reads or hard errors
2705
+ - check product docs do not contain engineering execution flow, generated file inventories, or CLI envelope mechanics
2706
+ - check engineering docs do not contain product promises, product rationale, or Product Decisions sections
2707
+ - never judge whether a product decision is commercially correct, valuable, prioritized, or desirable
2405
2708
  - check that route files map code surfaces to canonical truth docs when route files exist
2406
2709
  - check for broad, catch-all, index-like, or mixed-owner truth docs and report them as topology issues requiring Truth Structure
2407
- - check that canonical behavior docs keep active Product Decisions and Rationale sections
2710
+ - check that canonical docs keep lane-appropriate decisions and rationale sections
2408
2711
  - optionally run truthmark check when local tooling is available
2409
2712
  - must not require the truthmark binary; direct inspection is always valid
2410
2713
  - report issues and suggested fixes without silently rewriting unrelated files
@@ -2412,20 +2715,13 @@ Truth Check is agent-led:
2412
2715
  ${renderAuditEvidenceGateSection()}
2413
2716
 
2414
2717
  ${subagentMode}${renderHierarchySummary(config)}
2415
- ${DECISION_TRUTH_INSTRUCTIONS}
2416
-
2417
- Report completion in this shape:
2418
-
2419
- ${renderMarkdownExample(renderTruthCheckReportExample(config))}`;
2718
+ ${DECISION_TRUTH_INSTRUCTIONS}`;
2420
2719
  };
2421
2720
 
2422
2721
  // src/agents/truth-document.ts
2423
- var renderMarkdownExample2 = (content) => {
2424
- return ["```md", content, "```"].join("\n");
2425
- };
2426
2722
  var TRUTH_DOCUMENT_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-document; Codex /truthmark-document or $truthmark-document; Claude Code /truthmark-document; GitHub Copilot /truthmark-document; Gemini CLI /truthmark:document.";
2427
2723
  var renderTruthDocumentReportExample = (config = defaultAgentConfig()) => {
2428
- const truthDocsRoot = resolveTruthDocsRoot(config);
2724
+ const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2429
2725
  const helperScripts = ["validate-write-lease: skipped, no write lease used"];
2430
2726
  return `Truth Document: completed
2431
2727
 
@@ -2436,13 +2732,13 @@ Ownership reviewed:
2436
2732
  - ${config.truthmark.paths.routesIndex}
2437
2733
 
2438
2734
  Truth docs created:
2439
- - ${truthDocsRoot}/contracts.md
2735
+ - ${engineeringTruthRoot}/contracts/routing.md
2440
2736
 
2441
2737
  Truth docs updated:
2442
- - ${truthDocsRoot}/check-diagnostics.md
2738
+ - ${engineeringTruthRoot}/behaviors/check-diagnostics.md
2443
2739
 
2444
2740
  Truth docs restructured:
2445
- - ${truthDocsRoot}/check-diagnostics.md
2741
+ - ${engineeringTruthRoot}/behaviors/check-diagnostics.md
2446
2742
 
2447
2743
  Routing updated:
2448
2744
  - ${config.truthmark.paths.routesIndex}
@@ -2464,7 +2760,7 @@ ${helperScripts.map((helperScript) => `- ${helperScript}`).join("\n")}
2464
2760
  Notes:
2465
2761
  - Documented routing and behavior from route handlers and tests.`;
2466
2762
  };
2467
- var renderTruthDocumentSkillBody = (config = defaultAgentConfig(), options = {}) => {
2763
+ var renderTruthDocumentProcedureBody = (config = defaultAgentConfig(), options = {}) => {
2468
2764
  const workflow = getTruthmarkWorkflow("truthmark-document");
2469
2765
  const claudeSubagentMode = options.includeClaudeSubagentMode ? `${renderClaudeSubagentModeSection(
2470
2766
  workflow.subagents ?? [],
@@ -2491,15 +2787,7 @@ var renderTruthDocumentSkillBody = (config = defaultAgentConfig(), options = {})
2491
2787
  )}
2492
2788
  ` : "";
2493
2789
  const subagentMode = `${claudeSubagentMode}${codexSubagentMode}${copilotCustomAgentMode}${openCodeSubagentMode}`;
2494
- return `---
2495
- name: truthmark-document
2496
- description: ${workflow.description}
2497
- argument-hint: Optional implemented behavior, API endpoint, route, controller, package, or truth-doc area to document
2498
- user-invocable: true
2499
- truthmark-version: ${TRUTHMARK_VERSION}
2500
- ---
2501
-
2502
- # Truthmark Document
2790
+ return `# Truthmark Document
2503
2791
 
2504
2792
  Use this skill to document existing implemented behavior when no functional-code changes are required for the task.
2505
2793
  Invocations: ${TRUTH_DOCUMENT_EXPLICIT_INVOCATIONS}
@@ -2508,7 +2796,10 @@ Truth Document is manual and implementation-first:
2508
2796
 
2509
2797
  - run only when the user explicitly asks to generate or update truth docs for existing behavior, or when Truth Sync, Truth Check, or Truth Structure reports implemented behavior that lacks canonical truth docs
2510
2798
  - inspect .truthmark/config.yml and configured route files only when they exist; then inspect existing canonical docs, implementation code, and tests directly
2511
- - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2799
+ - Evidence authority:
2800
+ ${renderBulletBlock(EVIDENCE_AUTHORITY_INSTRUCTIONS)}
2801
+ - Lane classification:
2802
+ ${renderLaneClassificationRuleBlock(config)}
2512
2803
  - document current implemented behavior; do not invent future behavior or planned endpoints
2513
2804
  - may write canonical truth docs and ${config.truthmark.paths.routesIndex} or relevant child route files only
2514
2805
  - must not write functional code
@@ -2516,7 +2807,10 @@ Truth Document is manual and implementation-first:
2516
2807
  - block and recommend Truth Structure when routing repair is unsafe, ambiguous, or outside the task boundary
2517
2808
  - keep feature README.md files as indexes rather than truth-document targets
2518
2809
  - create or update bounded leaf truth docs when behavior does not fit an existing leaf doc
2519
- - keep behavior truth docs behavior-oriented, not endpoint-oriented, unless the endpoint itself is the behavior boundary
2810
+ - write product capability/boundary truth under ${config.truthmark.paths.productTruthRoot} when documenting product promise, boundary, rationale, or user/stakeholder value
2811
+ - write engineering truth under ${config.truthmark.paths.engineeringTruthRoot} when documenting implementation behavior, contracts, architecture, workflows, operations, or tests
2812
+ - for both-lane documentation requests, write separate product and engineering docs and cross-link them in route YAML with realized_by and realizes, not in doc frontmatter
2813
+ - keep engineering behavior truth behavior-oriented, not endpoint-oriented, unless the endpoint itself is the behavior boundary
2520
2814
  - keep API endpoint details in the nearest contract truth doc when such a doc owns the API contract
2521
2815
  - preserve unrelated authored content
2522
2816
  ${renderTruthDocOwnershipGateSection(
@@ -2546,19 +2840,12 @@ Parent post-document verification:
2546
2840
  - verify only truth docs and leased truth routing files changed during document work
2547
2841
  - block on functional code, generated host surfaces, or unrelated diffs caused by document work
2548
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
2549
- - verify the final report records ownership review, structure requirement, restructure, routing update, or blocked reason when applicable
2550
-
2551
- Report completion in this shape:
2552
- ${renderMarkdownExample2(renderTruthDocumentReportExample(config))}`;
2843
+ - verify the final report records ownership review, structure requirement, restructure, routing update, or blocked reason when applicable`;
2553
2844
  };
2554
2845
 
2555
2846
  // src/agents/truth-preview.ts
2556
- var renderMarkdownExample3 = (content) => {
2557
- return ["```md", content, "```"].join("\n");
2558
- };
2559
2847
  var TRUTH_PREVIEW_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-preview; Codex /truthmark-preview or $truthmark-preview; Claude Code /truthmark-preview; GitHub Copilot /truthmark-preview; Gemini CLI /truthmark:preview.";
2560
2848
  var renderTruthPreviewReportExample = (config = defaultAgentConfig()) => {
2561
- const truthDocsRoot = resolveTruthDocsRoot(config);
2562
2849
  return `Truth Preview: completed
2563
2850
 
2564
2851
  Requested outcome:
@@ -2574,14 +2861,16 @@ Why this workflow:
2574
2861
 
2575
2862
  Likely route owner:
2576
2863
  - route file: ${config.truthmark.paths.routesIndex}
2577
- - truth doc: ${truthDocsRoot}/example.md
2864
+ - likely lane impact: engineering-lane
2865
+ - product target docs: none identified
2866
+ - engineering target docs: ${config.truthmark.paths.engineeringTruthRoot}/behaviors/example.md
2578
2867
  - confidence: medium
2579
2868
 
2580
2869
  Expected write classes:
2581
2870
  - truth docs
2582
2871
 
2583
2872
  Expected target files:
2584
- - ${truthDocsRoot}/example.md
2873
+ - ${config.truthmark.paths.engineeringTruthRoot}/behaviors/example.md
2585
2874
 
2586
2875
  Suggested subagent use:
2587
2876
  - read-only verifiers: truth_route_auditor
@@ -2594,17 +2883,8 @@ Blocking ambiguity:
2594
2883
  Handoff:
2595
2884
  - Run the selected Truthmark workflow after user approval.`;
2596
2885
  };
2597
- var renderTruthPreviewSkillBody = (config = defaultAgentConfig()) => {
2598
- const workflow = getTruthmarkWorkflow("truthmark-preview");
2599
- return `---
2600
- name: truthmark-preview
2601
- description: ${workflow.description}
2602
- argument-hint: Optional requested outcome, code area, doc path, or routing question
2603
- user-invocable: true
2604
- truthmark-version: ${TRUTHMARK_VERSION}
2605
- ---
2606
-
2607
- Use this skill only when the user explicitly asks to preview Truthmark routing or workflow choice before edits.
2886
+ var renderTruthPreviewProcedureBody = (config = defaultAgentConfig()) => {
2887
+ return `Use this skill only when the user explicitly asks to preview Truthmark routing or workflow choice before edits.
2608
2888
 
2609
2889
  Invocations: ${TRUTH_PREVIEW_EXPLICIT_INVOCATIONS}
2610
2890
 
@@ -2612,15 +2892,19 @@ Truth Preview is read-only. Its report is intended, not authorized.
2612
2892
 
2613
2893
  Purpose:
2614
2894
  - preview the likely Truthmark workflow, route owner, target files, expected write classes, suggested subagent use, and blocking ambiguity before edits happen
2895
+ - report likely product lane impact, engineering lane impact, target docs, and ambiguity before edits
2615
2896
  - hand off to the selected workflow after user approval
2616
2897
  - keep the selector thin so agents can avoid loading or acting through heavier workflows prematurely
2617
2898
 
2618
2899
  Read:
2619
2900
  - .truthmark/config.yml, only when present
2620
- - ${config.truthmark.paths.routesIndex}, only when present
2621
- - relevant child route files under ${config.truthmark.paths.routeAreasRoot}/, only when present
2901
+ - ${config.truthmark.paths.routesIndex}, first, only when present
2902
+ - relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ for the selected scope or changed paths, only when present
2622
2903
  - relevant truth docs and implementation files needed to preview ownership
2623
- - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2904
+ - Evidence authority:
2905
+ ${renderBulletBlock(EVIDENCE_AUTHORITY_INSTRUCTIONS)}
2906
+ - Lane classification:
2907
+ ${renderReadOnlyLaneClassificationRuleBlock(config)}
2624
2908
 
2625
2909
  Do not:
2626
2910
  - must not edit files
@@ -2637,27 +2921,15 @@ Suggested subagent use:
2637
2921
  - write workers: none
2638
2922
  - leases needed: none
2639
2923
 
2640
- ${renderHierarchySummary(config)}
2641
-
2642
- Report completion in this shape:
2643
- ${renderMarkdownExample3(renderTruthPreviewReportExample(config))}`;
2924
+ ${renderHierarchySummary(config)}`;
2644
2925
  };
2645
2926
 
2646
2927
  // src/agents/truthmark-portal.ts
2647
2928
  var TRUTHMARK_PORTAL_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-portal; Codex /truthmark-portal or $truthmark-portal; Claude Code /truthmark-portal; GitHub Copilot /truthmark-portal; Gemini CLI /truthmark:portal.";
2648
- var renderTruthmarkPortalSkillBody = (config = defaultAgentConfig()) => {
2649
- const workflow = getTruthmarkWorkflow("truthmark-portal");
2929
+ var renderTruthmarkPortalProcedureBody = (config = defaultAgentConfig()) => {
2650
2930
  const output = config.truthmark.paths.portalOutput;
2651
2931
  const template = config.truthmark.paths.portalTemplate;
2652
- return `---
2653
- name: truthmark-portal
2654
- description: ${workflow.description}
2655
- argument-hint: Optional portal generation focus
2656
- user-invocable: true
2657
- truthmark-version: ${TRUTHMARK_VERSION}
2658
- ---
2659
-
2660
- # Truthmark Portal
2932
+ return `# Truthmark Portal
2661
2933
 
2662
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.
2663
2935
 
@@ -2688,48 +2960,19 @@ Workflow:
2688
2960
  5. Replace or write only under ${output}; do not edit canonical Markdown, routing, source code, or instruction files unless the user explicitly changes scope.
2689
2961
  6. Generate the multi-page static site with local assets/search metadata and visible source provenance.
2690
2962
  7. Validate entry page, links where practical, provenance/disclaimers, local-only assets, and that metadata remains under ${output}/assets.
2691
- ${renderHierarchySummary(config)}
2692
-
2693
- Report completion in this shape:
2694
-
2695
- \`\`\`md
2696
- Truthmark Portal: completed
2697
-
2698
- Output path:
2699
- - ${output}
2700
-
2701
- Page count:
2702
- - <count>
2703
-
2704
- Diagrams/assets:
2705
- - <generated diagrams/assets or none>
2706
-
2707
- Source docs reviewed:
2708
- - <source markdown paths>
2709
-
2710
- Skipped/ambiguous docs:
2711
- - <paths and reason, or none>
2712
-
2713
- Validation:
2714
- - <checks performed>
2715
-
2716
- Markdown canonical statement:
2717
- - Markdown remains canonical; generated Portal HTML is non-canonical presentation only.
2718
- \`\`\`
2719
- `;
2963
+ ${renderHierarchySummary(config)}`;
2720
2964
  };
2721
2965
 
2722
2966
  // src/agents/truth-structure.ts
2723
- var renderMarkdownExample4 = (content) => {
2724
- return ["```md", content, "```"].join("\n");
2725
- };
2726
2967
  var TRUTH_STRUCTURE_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-structure; Codex /truthmark-structure or $truthmark-structure; Claude Code /truthmark-structure; GitHub Copilot /truthmark-structure; Gemini CLI /truthmark:structure.";
2727
2968
  var renderTruthStructureReportExample = (config = defaultAgentConfig()) => {
2728
- const truthDocsRoot = resolveTruthDocsRoot(config);
2969
+ const productTruthRoot = resolveProductTruthRoot(config);
2970
+ const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2729
2971
  return `Truth Structure: completed
2730
2972
  Topology reviewed:
2731
2973
  - controllers: src/auth/**
2732
- - docs root: ${truthDocsRoot}
2974
+ - product docs root: ${productTruthRoot}
2975
+ - engineering docs root: ${engineeringTruthRoot}
2733
2976
  - route files: ${config.truthmark.paths.routesIndex}
2734
2977
  Areas reviewed:
2735
2978
  - src/auth/**
@@ -2738,14 +2981,16 @@ Routing updated:
2738
2981
  Initial truth boundary:
2739
2982
  - Area: Authentication
2740
2983
  - Code: src/auth/**
2741
- - Truth owner: ${truthDocsRoot}/authentication/session.md
2984
+ - Product owner: ${productTruthRoot}/capabilities/authentication-session.md
2985
+ - Engineering owner: ${engineeringTruthRoot}/behaviors/authentication-session.md
2742
2986
  - Scope: session behavior only
2743
2987
  Truth docs created:
2744
- - ${truthDocsRoot}/authentication/session.md
2988
+ - ${productTruthRoot}/capabilities/authentication-session.md
2989
+ - ${engineeringTruthRoot}/behaviors/authentication-session.md
2745
2990
  Truth docs split:
2746
- - ${truthDocsRoot}/authentication/README.md -> ${truthDocsRoot}/authentication/session.md
2991
+ - docs/truthmark/truth/authentication/README.md -> ${productTruthRoot}/capabilities/authentication-session.md and ${engineeringTruthRoot}/behaviors/authentication-session.md
2747
2992
  Truth docs restructured:
2748
- - ${truthDocsRoot}/authentication/README.md
2993
+ - docs/truthmark/truth/authentication/README.md
2749
2994
  ${renderClaimEvidenceCheckedSection([
2750
2995
  {
2751
2996
  claim: "Session behavior belongs to a dedicated Authentication truth owner.",
@@ -2758,8 +3003,9 @@ Topology decisions:
2758
3003
  Notes:
2759
3004
  - Added an Authentication area for session behavior.`;
2760
3005
  };
2761
- var renderTruthStructureSkillBody = (config = defaultAgentConfig(), options = {}) => {
2762
- const truthDocsRoot = resolveTruthDocsRoot(config);
3006
+ var renderTruthStructureProcedureBody = (config = defaultAgentConfig(), options = {}) => {
3007
+ const productTruthRoot = resolveProductTruthRoot(config);
3008
+ const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2763
3009
  const workflow = getTruthmarkWorkflow("truthmark-structure");
2764
3010
  const claudeSubagentMode = options.includeClaudeSubagentMode ? `${renderClaudeSubagentModeSection(
2765
3011
  workflow.subagents ?? [],
@@ -2772,30 +3018,26 @@ var renderTruthStructureSkillBody = (config = defaultAgentConfig(), options = {}
2772
3018
  )}
2773
3019
  ` : "";
2774
3020
  const subagentMode = `${claudeSubagentMode}${copilotCustomAgentMode}`;
2775
- return `---
2776
- name: truthmark-structure
2777
- description: ${workflow.description}
2778
- argument-hint: Optional area, directory, or routing concern
2779
- user-invocable: true
2780
- truthmark-version: ${TRUTHMARK_VERSION}
2781
- ---
2782
-
2783
- Use this skill to design or repair Truthmark area structure.
3021
+ return `Use this skill to design or repair Truthmark area structure.
2784
3022
  Invocations: ${TRUTH_STRUCTURE_EXPLICIT_INVOCATIONS}
2785
3023
  Truth Structure is agent-native:
2786
3024
  - inspect repository layout, current docs, Truthmark config and route files when present, and relevant code directly
2787
- - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
3025
+ - Evidence authority:
3026
+ ${renderBulletBlock(EVIDENCE_AUTHORITY_INSTRUCTIONS)}
3027
+ - Lane classification:
3028
+ ${renderLaneClassificationRuleBlock(config)}
2788
3029
  - inspect the configured root route index at ${config.truthmark.paths.routesIndex} and relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ when they exist
2789
3030
  - define areas by product or behavior ownership, not by mechanical directory mirroring
2790
3031
  - create or repair ${config.truthmark.paths.routesIndex}
2791
3032
  - create starter truth docs when useful and when they belong in the canonical current-truth surface
2792
- - Starter truth docs must use closed YAML frontmatter bounded by opening and closing --- lines; include status, doc_type, last_reviewed, and source_of_truth inside that frontmatter.
2793
- - Starter truth docs must include ## Product Decisions and ## Rationale sections.
3033
+ - Starter truth docs must use closed YAML frontmatter bounded by opening and closing --- lines; include status, truth_kind, and last_reviewed inside that frontmatter. Put source references in the final ## Source References section, not in frontmatter.
3034
+ - Starter truth docs must use lane-specific templates and keep product and engineering truth in separate files.
2794
3035
  ${subagentMode}
2795
3036
  ${FEATURE_DOC_TEMPLATE_INSTRUCTIONS}
2796
- - use ${truthDocsRoot}/** for current truth destinations
3037
+ - use ${productTruthRoot}/** for product truth destinations
3038
+ - use ${engineeringTruthRoot}/** for engineering truth destinations
2797
3039
  - use only canonical current-truth destinations for starter truth docs
2798
- - keep active Product Decisions and Rationale in the canonical doc that owns the behavior
3040
+ - keep active Product Decisions in product truth and Engineering Decisions in engineering truth
2799
3041
  - preserve unrelated authored content
2800
3042
  ## New area setup
2801
3043
  Use when a user asks to onboard a new code area into Truthmark, a new package, controller, domain, or product area lacks bounded truth ownership, or a new product area needs routing and starter truth docs.
@@ -2813,7 +3055,7 @@ Do not:
2813
3055
  - do not create generic catch-all docs
2814
3056
  - do not treat README files as Sync targets
2815
3057
  ## Topology Governance
2816
- Truth Structure owns documentation topology. Do not depend on humans to manually organize ${truthDocsRoot}. Treat the configured truth root as a managed semantic root.
3058
+ Truth Structure owns documentation topology, lane splits, decision relocation, and relationship repair. Do not depend on humans to manually organize ${productTruthRoot} or ${engineeringTruthRoot}. Treat both configured lane roots as managed semantic roots.
2817
3059
  Inspect controllers, routes, handlers, services, packages, tests, existing truth docs, and route files; infer product and domain ownership from behavior boundaries, not from mechanical directory mirroring.
2818
3060
  When topology pressure exists, repair structure before creating or extending truth docs.
2819
3061
  ${renderTruthDocOwnershipGateSection(
@@ -2825,7 +3067,7 @@ Topology pressure signals:
2825
3067
  - one area maps broad code such as src/**, app/**, server/**, services/**, or packages/**
2826
3068
  - one area maps multiple unrelated controllers, route groups, services, or bounded contexts
2827
3069
  - one truth doc owns unrelated behaviors or unrelated endpoint families
2828
- - the configured truth root has many direct non-index docs
3070
+ - either configured lane root has many direct non-index docs
2829
3071
  - a changed controller, route, or service cannot map to a specific behavior doc
2830
3072
  - Truth Sync would need to create a new generic truth doc because routing is too broad
2831
3073
  - endpoint or controller names reveal domains missing from ${config.truthmark.paths.routeAreasRoot}/**
@@ -2838,9 +3080,10 @@ Repair rules:
2838
3080
  - split broad, overloaded, or catch-all areas into behavior-owned child route files
2839
3081
  - split mixed-owner truth docs into bounded owner docs before adding new behavior claims
2840
3082
  - create route files under ${config.truthmark.paths.routeAreasRoot}/ when a product/domain boundary is clear
2841
- - create behavior truth docs under the configured truth root only when behavior lacks a current doc
3083
+ - create engineering behavior truth docs under ${engineeringTruthRoot} only when behavior lacks a current doc
3084
+ - create product truth docs under ${productTruthRoot} only when product promise, boundary, rationale, or user-visible capability truth is in scope
2842
3085
  - README.md files are indexes, not Truth Sync targets
2843
- - prefer bounded leaf truth docs at <truth-root>/<domain>/<behavior>.md
3086
+ - prefer bounded product docs under product/capabilities or product/decisions and engineering docs under engineering/<kind>/<surface>.md
2844
3087
  - keep behavior truth docs behavior-oriented, not endpoint-oriented
2845
3088
  - keep API endpoint details in the nearest contract truth doc when such a doc exists
2846
3089
  - update routing so future Truth Sync can target small docs
@@ -2851,17 +3094,15 @@ ${renderTruthDocRestructureGateSection(
2851
3094
  )}
2852
3095
  ${renderTopologyEvidenceGateSection()}
2853
3096
  ${ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS}
2854
- - Do not finish topology repair with routed canonical current-truth docs missing Product Decisions or Rationale sections.
2855
- - If an existing canonical doc lacks either section, add the missing heading beside Current Behavior with a concise current-state placeholder or active decision.
3097
+ - Do not finish topology repair with mixed product/engineering authority in a single canonical truth doc.
3098
+ - If an existing canonical doc has wrong-lane sections, split or move them into the correct product or engineering lane.
2856
3099
  Portable fallback:
2857
3100
  - If this skill surface is unavailable, perform the same workflow directly from committed repository files.
2858
3101
  - Do not require the truthmark CLI.
2859
3102
  - Inspect .truthmark/config.yml and configured route files only when they exist; then inspect canonical docs and representative implementation code.
2860
3103
  - Use a subagent only when the host supports that pattern; otherwise perform the topology repair inline.
2861
3104
  ${renderHierarchySummary(config)}
2862
- ${DECISION_TRUTH_INSTRUCTIONS}
2863
- Report completion in this shape:
2864
- ${renderMarkdownExample4(renderTruthStructureReportExample(config))}`;
3105
+ ${DECISION_TRUTH_INSTRUCTIONS}`;
2865
3106
  };
2866
3107
 
2867
3108
  // src/sync/report.ts
@@ -2975,13 +3216,8 @@ var renderTruthSyncBlockedReport = (input) => {
2975
3216
 
2976
3217
  // src/agents/truth-sync.ts
2977
3218
  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.";
2978
- var renderMarkdownExample5 = (content) => {
2979
- return ["```md", content, "```"].join("\n");
2980
- };
2981
- var renderTruthSyncSkillBody = (config = defaultAgentConfig(), options = {}) => {
2982
- const truthDocsRoot = resolveTruthDocsRoot(config);
3219
+ var renderTruthSyncProcedureBody = (config = defaultAgentConfig(), options = {}) => {
2983
3220
  const workflow = getTruthmarkWorkflow("truthmark-sync");
2984
- const helperScripts = ["validate-write-lease: skipped, no write lease used"];
2985
3221
  const claudeSubagentMode = options.includeClaudeSubagentMode ? `${renderClaudeSubagentModeSection(
2986
3222
  workflow.subagents ?? [],
2987
3223
  "Parent agent owns Truth Sync acceptance, lease validation, and final report",
@@ -3007,15 +3243,7 @@ var renderTruthSyncSkillBody = (config = defaultAgentConfig(), options = {}) =>
3007
3243
  )}
3008
3244
  ` : "";
3009
3245
  const subagentMode = `${claudeSubagentMode}${codexSubagentMode}${copilotCustomAgentMode}${openCodeSubagentMode}`;
3010
- return `---
3011
- name: truthmark-sync
3012
- description: ${workflow.description}
3013
- argument-hint: Optional changed-code area, truth-doc area, or sync focus
3014
- user-invocable: true
3015
- truthmark-version: ${TRUTHMARK_VERSION}
3016
- ---
3017
-
3018
- 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.
3246
+ 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.
3019
3247
  Invocations: ${TRUTH_SYNC_EXPLICIT_INVOCATIONS}
3020
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.
3021
3249
  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.
@@ -3023,9 +3251,13 @@ Parent workflow:
3023
3251
  1. Inspect git status, staged changes, unstaged changes, and untracked files directly.
3024
3252
  2. Inspect .truthmark/config.yml and configured route files only when they exist; then inspect relevant canonical docs.
3025
3253
  3. Identify functional-code changes and the nearest truth docs or routing repairs.
3026
- 4. ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
3027
- 5. Code verification is parent-owned: follow repository instructions and task context, and report what ran or why it did not run.
3028
- 6. 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.
3254
+ 4. Evidence authority:
3255
+ ${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.
3029
3261
  ${subagentMode}Topology quality gate:
3030
3262
  - before updating truth docs, verify the changed code resolves to a specific behavior-owned area and bounded truth owner
3031
3263
  - if routing is missing, stale, broad, overloaded, catch-all route only, or cannot map changed code to a bounded truth owner, do not create another generic truth doc
@@ -3035,6 +3267,7 @@ ${subagentMode}Topology quality gate:
3035
3267
  - README.md files are indexes, not Truth Sync targets
3036
3268
  - must not append behavior details to a README.md index
3037
3269
  - create or update a bounded leaf truth doc when behavior changes do not fit an existing leaf doc
3270
+ - 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
3038
3271
  ${renderTruthDocOwnershipGateSection(
3039
3272
  "changed functional files and impacted truth docs",
3040
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"
@@ -3042,7 +3275,7 @@ ${renderTruthDocOwnershipGateSection(
3042
3275
  ${TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS}
3043
3276
  ${FEATURE_DOC_TEMPLATE_INSTRUCTIONS}
3044
3277
  ${renderTruthDocRestructureGateSection(
3045
- "Truth Sync may restructure only truth docs impacted by the current functional-code change."
3278
+ "Truth Sync may restructure leased canonical truth docs when the current sync evidence shows repository truth is stale, even when the doc is outside the initially affected route focus."
3046
3279
  )}
3047
3280
  ${ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS}
3048
3281
  ${renderRouteFirstEvidenceGateSection(
@@ -3054,7 +3287,7 @@ Optional validation tooling:
3054
3287
  - you may run truthmark check when local tooling is available
3055
3288
  - do not require the truthmark binary; direct checkout inspection is the canonical path
3056
3289
  - optional validation must not replace agent judgment about docs and routing
3057
- - update Product Decisions and Rationale when a behavior change comes from a decision change
3290
+ - update Product Decisions only in product truth and Engineering Decisions only in engineering truth when evidence supports the lane-specific decision change
3058
3291
  Helper status reporting:
3059
3292
  - Validate the report body before adding this validator's own success status; the body may omit \`validate-sync-report\` while validation is pending.
3060
3293
  - After \`truthmark validate sync-report <report-file> --json\` returns \`data.validation.ok: true\`, append or update \`validate-sync-report: ran, passed\` in the final report.
@@ -3069,34 +3302,9 @@ Parent post-sync verification:
3069
3302
  - block if functional code changed during sync
3070
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
3071
3304
  - 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
3072
- - verify the updated docs correspond to the reviewed changed-code surface
3305
+ - verify the updated docs correspond to reviewed checkout evidence, changed-code impact, or a recorded stale-truth correction made within the sync write lease
3073
3306
  - verify the final report records ownership review, structure requirement, split, restructure, or blocked reason when the ownership gate fired
3074
- - blocked outcomes must preserve the working tree as-is: no rollback, no post-block cleanup edits, and manual-review reporting of any remaining files
3075
- Report completion in this shape:
3076
- ${renderMarkdownExample5(
3077
- renderTruthSyncCompletedReport({
3078
- changedCode: ["src/auth/session.ts"],
3079
- ownershipReviewed: [config.truthmark.paths.routesIndex],
3080
- truthDocsUpdated: [`${truthDocsRoot}/repository/overview.md`],
3081
- evidenceChecked: [
3082
- {
3083
- claim: "Session timeout behavior is documented in the mapped repository truth doc.",
3084
- evidence: ["src/auth/session.ts:12", `${config.truthmark.paths.routesIndex}:11`],
3085
- result: "supported"
3086
- }
3087
- ],
3088
- helperScripts,
3089
- notes: ["Updated session timeout behavior."]
3090
- })
3091
- )}
3092
- Blocked report example:
3093
- ${renderMarkdownExample5(
3094
- renderTruthSyncBlockedReport({
3095
- reason: "routing repair is not allowed",
3096
- manualReviewFiles: [config.truthmark.paths.routesIndex],
3097
- nextAction: "update routing metadata and rerun Truth Sync"
3098
- })
3099
- )}`;
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`;
3100
3308
  };
3101
3309
 
3102
3310
  // src/agents/write-lease.ts
@@ -3182,7 +3390,31 @@ description: '${description}'
3182
3390
  ${prompt}
3183
3391
  `;
3184
3392
  };
3185
- var renderTomlString = (value) => {
3393
+ var workflowSupportFiles = (workflowId) => {
3394
+ const workflow = getTruthmarkWorkflow(workflowId);
3395
+ const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
3396
+ const hasSubagentSupport = definition.parentRule !== void 0 && ((workflow.subagents?.length ?? 0) > 0 || (workflow.writeSubagents?.length ?? 0) > 0);
3397
+ const hasHelperSupport = (workflow.helpers?.length ?? 0) > 0;
3398
+ return [
3399
+ "support/procedure.md",
3400
+ "support/report-template.md",
3401
+ ...hasSubagentSupport ? ["support/subagents-and-leases.md"] : [],
3402
+ ...hasHelperSupport ? ["helper-manifest.yml", "support/helper-policy.md"] : []
3403
+ ];
3404
+ };
3405
+ var renderWorkflowCommandAdapterInstructions = (workflowId, root, hostName, surfaceKind) => {
3406
+ const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
3407
+ const canonicalFiles = ["SKILL.md", ...workflowSupportFiles(workflowId)].map((supportFile) => `- ${root}/${supportFile}`).join("\n");
3408
+ return `This ${surfaceKind} is the ${hostName} entrypoint for ${definition.title}.
3409
+
3410
+ Do not invoke another Truthmark command from here.
3411
+
3412
+ Read these host-local files in order only as needed:
3413
+ ${canonicalFiles}
3414
+
3415
+ If skill entrypoints are unavailable, use the host's direct evidence-first manual fallback procedure.`;
3416
+ };
3417
+ var renderTomlString = (value) => {
3186
3418
  return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"')}"`;
3187
3419
  };
3188
3420
  var renderTomlStringArray = (values) => {
@@ -3246,7 +3478,7 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3246
3478
  use: () => "Use this skill only when the user explicitly asks to preview Truthmark routing or workflow choice before edits.",
3247
3479
  quickRules: (config) => [
3248
3480
  "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
3249
- `Inspect .truthmark/config.yml and configured route files (${routeFilesHint(config)}) only when they exist; then inspect only the truth docs or implementation files needed to preview ownership.`,
3481
+ `Inspect .truthmark/config.yml and the root route index (${config.truthmark.paths.routesIndex}) first when present; then inspect only child route files under ${config.truthmark.paths.routeAreasRoot}/ that are relevant to the selected scope or changed paths, plus the truth docs or implementation files needed to preview ownership.`,
3250
3482
  "Truth Preview is read-only; this report is intended, not authorized.",
3251
3483
  "must not edit files and must not issue write leases; do not run Truth Sync automatically, replace Truth Check, claim final correctness, or mutate code.",
3252
3484
  "Use optional read-only route-auditor evidence only when it reduces context or clarifies ownership.",
@@ -3302,22 +3534,110 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3302
3534
  ]
3303
3535
  }
3304
3536
  };
3305
- var stripWorkflowSkillFrontmatter = (body) => {
3306
- return body.replace(/^---\n[\s\S]*?\n---\n\n?/u, "").trim();
3537
+ var renderMarkdownExample = (content) => {
3538
+ return ["```md", content, "```"].join("\n");
3307
3539
  };
3308
- var splitWorkflowSupport = (body) => {
3309
- const stripped = stripWorkflowSkillFrontmatter(body);
3310
- const marker = "Report completion in this shape:";
3311
- const markerIndex = stripped.indexOf(marker);
3312
- if (markerIndex === -1) {
3313
- return {
3314
- procedure: stripped,
3315
- reportTemplate: "Report completion in the workflow-specific shape."
3316
- };
3540
+ var renderWorkflowReportTemplate = (workflowId, config) => {
3541
+ const productTruthRoot = resolveProductTruthRoot(config);
3542
+ const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
3543
+ switch (workflowId) {
3544
+ case "truthmark-structure":
3545
+ return `Report completion in this shape:
3546
+ ${renderMarkdownExample(
3547
+ renderTruthStructureReportExample(config)
3548
+ )}`;
3549
+ case "truthmark-document":
3550
+ return `Report completion in this shape:
3551
+ ${renderMarkdownExample(
3552
+ renderTruthDocumentReportExample(config)
3553
+ )}`;
3554
+ case "truthmark-sync":
3555
+ return `Report completion in this shape:
3556
+ ${renderMarkdownExample(
3557
+ renderTruthSyncCompletedReport({
3558
+ changedCode: ["src/auth/session.ts"],
3559
+ ownershipReviewed: [config.truthmark.paths.routesIndex],
3560
+ truthDocsUpdated: [`${engineeringTruthRoot}/repository/overview.md`],
3561
+ evidenceChecked: [
3562
+ {
3563
+ claim: "Session timeout behavior is documented in the mapped repository truth doc.",
3564
+ evidence: [
3565
+ "src/auth/session.ts:12",
3566
+ `${config.truthmark.paths.routesIndex}:11`
3567
+ ],
3568
+ result: "supported"
3569
+ }
3570
+ ],
3571
+ helperScripts: [
3572
+ "validate-write-lease: skipped, no write lease used"
3573
+ ],
3574
+ notes: ["Updated session timeout behavior."]
3575
+ })
3576
+ )}
3577
+ Blocked report example:
3578
+ ${renderMarkdownExample(
3579
+ renderTruthSyncBlockedReport({
3580
+ reason: "routing repair is not allowed",
3581
+ manualReviewFiles: [config.truthmark.paths.routesIndex],
3582
+ nextAction: "update routing metadata and rerun Truth Sync"
3583
+ })
3584
+ )}`;
3585
+ case "truthmark-preview":
3586
+ return `Report completion in this shape:
3587
+ ${renderMarkdownExample(
3588
+ renderTruthPreviewReportExample(config)
3589
+ )}`;
3590
+ case "truthmark-realize":
3591
+ return `Report completion in this shape:
3592
+
3593
+ ${renderMarkdownExample(`Truth Realize: completed
3594
+
3595
+ Truth docs used:
3596
+ - ${productTruthRoot}/capabilities/authentication-session.md
3597
+ - ${engineeringTruthRoot}/behaviors/authentication-session.md
3598
+
3599
+ Code updated:
3600
+ - src/auth/session.ts
3601
+
3602
+ Verification:
3603
+ - npm test -- auth`)}`;
3604
+ case "truthmark-check":
3605
+ return `Report completion in this shape:
3606
+
3607
+ ${renderMarkdownExample(
3608
+ renderTruthCheckReportExample(config)
3609
+ )}`;
3610
+ case "truthmark-portal":
3611
+ return `Report completion in this shape:
3612
+
3613
+ ${renderMarkdownExample(`Truthmark Portal: completed
3614
+
3615
+ Output path:
3616
+ - ${config.truthmark.paths.portalOutput}
3617
+
3618
+ Page count:
3619
+ - <count>
3620
+
3621
+ Diagrams/assets:
3622
+ - <generated diagrams/assets or none>
3623
+
3624
+ Source docs reviewed:
3625
+ - <source markdown paths>
3626
+
3627
+ Skipped/ambiguous docs:
3628
+ - <paths and reason, or none>
3629
+
3630
+ Validation:
3631
+ - <checks performed>
3632
+
3633
+ Markdown canonical statement:
3634
+ - Markdown remains canonical; generated Portal HTML is non-canonical presentation only.`)}`;
3317
3635
  }
3636
+ };
3637
+ var renderWorkflowSupportParts = (workflowId, config) => {
3318
3638
  return {
3319
- procedure: stripped.slice(0, markerIndex).trim(),
3320
- reportTemplate: stripped.slice(markerIndex).trim()
3639
+ procedure: renderWorkflowProcedure(workflowId, config),
3640
+ reportTemplate: renderWorkflowReportTemplate(workflowId, config)
3321
3641
  };
3322
3642
  };
3323
3643
  var renderSkillSupportFile = (title, body) => {
@@ -3378,22 +3698,22 @@ Helper scripts:
3378
3698
  \`\`\``
3379
3699
  );
3380
3700
  };
3381
- var renderStandaloneWorkflowSkillBody = (workflowId, config) => {
3701
+ var renderWorkflowProcedure = (workflowId, config) => {
3382
3702
  switch (workflowId) {
3383
3703
  case "truthmark-structure":
3384
- return renderTruthStructureSkillBody(config);
3704
+ return renderTruthStructureProcedureBody(config);
3385
3705
  case "truthmark-document":
3386
- return renderTruthDocumentSkillBody(config);
3706
+ return renderTruthDocumentProcedureBody(config);
3387
3707
  case "truthmark-sync":
3388
- return renderTruthSyncSkillBody(config);
3708
+ return renderTruthSyncProcedureBody(config);
3389
3709
  case "truthmark-preview":
3390
- return renderTruthPreviewSkillBody(config);
3710
+ return renderTruthPreviewProcedureBody(config);
3391
3711
  case "truthmark-realize":
3392
- return renderTruthmarkRealizeSkillBody(config);
3712
+ return renderTruthmarkRealizeProcedureBody(config);
3393
3713
  case "truthmark-check":
3394
- return renderTruthCheckSkillBody(config);
3714
+ return renderTruthCheckProcedureBody(config);
3395
3715
  case "truthmark-portal":
3396
- return renderTruthmarkPortalSkillBody(config);
3716
+ return renderTruthmarkPortalProcedureBody(config);
3397
3717
  }
3398
3718
  };
3399
3719
  var renderWorkflowEntrypoint = (workflowId, config, supportFiles, host) => {
@@ -3494,17 +3814,13 @@ var renderTruthmarkSkillPackage = ({
3494
3814
  }) => {
3495
3815
  const skillDirectory = skillPath.replace(/\/SKILL\.md$/u, "");
3496
3816
  const supportDirectory = `${skillDirectory}/support`;
3497
- const { procedure, reportTemplate } = splitWorkflowSupport(
3498
- renderStandaloneWorkflowSkillBody(workflowId, config)
3817
+ const { procedure, reportTemplate } = renderWorkflowSupportParts(
3818
+ workflowId,
3819
+ config
3499
3820
  );
3500
3821
  const subagents = renderWorkflowSubagentSupport(workflowId, host);
3501
3822
  const helpers = getTruthmarkWorkflow(workflowId).helpers ?? [];
3502
- const supportFiles = [
3503
- "support/procedure.md",
3504
- "support/report-template.md",
3505
- ...subagents === void 0 ? [] : ["support/subagents-and-leases.md"],
3506
- ...helpers.length === 0 ? [] : ["helper-manifest.yml", "support/helper-policy.md"]
3507
- ];
3823
+ const supportFiles = workflowSupportFiles(workflowId);
3508
3824
  const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
3509
3825
  const files = [
3510
3826
  {
@@ -3549,16 +3865,19 @@ var renderTruthmarkSkillPackage = ({
3549
3865
  }
3550
3866
  return files;
3551
3867
  };
3552
- var normalizeOpenCodePermissionPath = (path13) => {
3553
- const normalized = path13.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
3868
+ var normalizeOpenCodePermissionPath = (path12) => {
3869
+ const normalized = path12.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
3554
3870
  return normalized === "" ? "." : normalized;
3555
3871
  };
3556
3872
  var appendOpenCodePermissionGlob = (root, glob) => {
3557
3873
  return root === "." ? glob.replace(/^\//u, "") : `${root}${glob}`;
3558
3874
  };
3559
3875
  var renderOpenCodeWriterEditAllowRules = (config) => {
3560
- const truthDocsRoot = normalizeOpenCodePermissionPath(
3561
- resolveTruthDocsRoot(config)
3876
+ const truthDocRoots = Array.from(
3877
+ /* @__PURE__ */ new Set([
3878
+ normalizeOpenCodePermissionPath(resolveProductTruthRoot(config)),
3879
+ normalizeOpenCodePermissionPath(resolveEngineeringTruthRoot(config))
3880
+ ])
3562
3881
  );
3563
3882
  const rootRouteIndex = normalizeOpenCodePermissionPath(
3564
3883
  config.truthmark.paths.routesIndex
@@ -3567,7 +3886,7 @@ var renderOpenCodeWriterEditAllowRules = (config) => {
3567
3886
  config.truthmark.paths.routeAreasRoot
3568
3887
  );
3569
3888
  const allowedPatterns = [
3570
- appendOpenCodePermissionGlob(truthDocsRoot, "/**"),
3889
+ ...truthDocRoots.map((root) => appendOpenCodePermissionGlob(root, "/**")),
3571
3890
  rootRouteIndex,
3572
3891
  appendOpenCodePermissionGlob(areaFilesRoot, "/**/*.md")
3573
3892
  ];
@@ -3590,7 +3909,14 @@ var TRUTHMARK_SUBAGENT_PROFILES = {
3590
3909
  instructions: `Stay read-only.
3591
3910
  Audit one bounded Truthmark route, area, or doc shard assigned by the parent.
3592
3911
  Inspect .truthmark/config.yml and route files only when they exist; then inspect mapped truth docs and relevant implementation files directly.
3912
+ Use a route-first bounded strategy: narrow audits inspect only the routed area and directly linked counterpart docs; root-wide health first builds a cheap route-map/index from route files, then inspects only mismatches and linked leaves.
3593
3913
  Find missing, stale, broad, overloaded, catch-all, mixed-owner, or unrouteable ownership.
3914
+ Validate route ownership against lane-specific roots and route kind:
3915
+ - confirm mapped truth docs resolve to the correct lane root (product or engineering) for their kind
3916
+ - flag mismatch between assigned route kind and resolved doc kind (for example, product-capability routed to engineering paths)
3917
+ - verify route-doc linkage for lane pairings via realized_by and realizes before recommending edits
3918
+ - inspect product counterparts for engineering docs only when route YAML claims a product relationship, or when the parent explicitly asks for user-visible product coverage
3919
+ - treat missing product links for user-visible engineering docs as a second-pass diagnostic, not a default full-document read.
3594
3920
  Do not edit files, stage changes, or propose broad rewrites.
3595
3921
  Return JSON only with keys: scope, filesReviewed, findings, evidence, confidence, recommendedWorkflow, notes.
3596
3922
  recommendedWorkflow must be one of: none, truthmark-document, truthmark-structure.`
@@ -3614,7 +3940,7 @@ Return JSON only with keys: scope, filesReviewed, claimsChecked, evidence, unsup
3614
3940
  description: "Read-only Truthmark doc reviewer for shape, decision, rationale, and evidence hygiene.",
3615
3941
  nicknameCandidates: ["Doc Audit", "Doc Shape", "Doc Check"],
3616
3942
  instructions: `Stay read-only.
3617
- Review assigned canonical truth docs for frontmatter, source_of_truth, required template sections, Evidence checked entries, Product Decisions, and Rationale.
3943
+ Review assigned canonical truth docs for compact frontmatter, required template sections, final Source References entries, Evidence checked entries, and lane-appropriate decision sections (Product Decisions in product truth, Engineering Decisions in engineering truth).
3618
3944
  Flag README.md files used as behavior truth targets, mixed-owner docs, and shape repairs that should move to Truth Structure.
3619
3945
  Do not edit files, stage changes, or rewrite docs.
3620
3946
  Return JSON only with keys: scope, filesReviewed, findings, evidence, confidence, recommendedWorkflow, notes.
@@ -4027,18 +4353,8 @@ truthmark:
4027
4353
  refresh_command: "truthmark init"
4028
4354
  `;
4029
4355
  };
4030
- var renderTruthmarkRealizeSkillBody = (config = defaultAgentConfig()) => {
4031
- const truthDocsRoot = resolveTruthDocsRoot(config);
4032
- const workflow = getTruthmarkWorkflow("truthmark-realize");
4033
- return `---
4034
- name: truthmark-realize
4035
- description: ${workflow.description}
4036
- argument-hint: Optional truth doc path, area, or desired code behavior to realize
4037
- user-invocable: true
4038
- truthmark-version: ${TRUTHMARK_VERSION}
4039
- ---
4040
-
4041
- # Truthmark Realize
4356
+ var renderTruthmarkRealizeProcedureBody = (config = defaultAgentConfig()) => {
4357
+ return `# Truthmark Realize
4042
4358
 
4043
4359
  Use this skill only when the user explicitly asks to realize truth docs into code.
4044
4360
 
@@ -4069,23 +4385,7 @@ Read and write boundaries:
4069
4385
 
4070
4386
  - may read truth docs, routing docs, and relevant functional code
4071
4387
  - may write functional code only
4072
- - must not edit truth docs or truth routing while realizing those docs
4073
-
4074
- Report completion in this shape:
4075
-
4076
- \`\`\`md
4077
- Truth Realize: completed
4078
-
4079
- Truth docs used:
4080
- - ${truthDocsRoot}/authentication/session-timeout.md
4081
-
4082
- Code updated:
4083
- - src/auth/session.ts
4084
-
4085
- Verification:
4086
- - npm test -- auth
4087
- \`\`\`
4088
- `;
4388
+ - must not edit truth docs or truth routing while realizing those docs`;
4089
4389
  };
4090
4390
  var renderTruthmarkRealizeSkillMetadata = () => {
4091
4391
  const workflow = getTruthmarkWorkflow("truthmark-realize");
@@ -4147,110 +4447,126 @@ truthmark:
4147
4447
  refresh_command: "truthmark init"
4148
4448
  `;
4149
4449
  };
4150
- var renderTruthmarkGeminiStructureCommand = (config = defaultAgentConfig()) => {
4151
- const workflow = getTruthmarkWorkflow("truthmark-structure");
4450
+ var renderGeminiWorkflowCommand = (workflowId, root) => {
4451
+ const workflow = getTruthmarkWorkflow(workflowId);
4152
4452
  return renderGeminiCommand(
4153
4453
  workflow.description,
4154
- renderTruthStructureSkillBody(config)
4454
+ renderWorkflowCommandAdapterInstructions(
4455
+ workflowId,
4456
+ root,
4457
+ "Gemini CLI",
4458
+ "command"
4459
+ )
4155
4460
  );
4156
4461
  };
4157
- var renderTruthmarkGeminiDocumentCommand = (config = defaultAgentConfig()) => {
4158
- const workflow = getTruthmarkWorkflow("truthmark-document");
4159
- return renderGeminiCommand(
4462
+ var renderCopilotWorkflowPrompt = (workflowId, root) => {
4463
+ const workflow = getTruthmarkWorkflow(workflowId);
4464
+ return renderCopilotPromptFile(
4160
4465
  workflow.description,
4161
- renderTruthDocumentSkillBody(config)
4466
+ renderWorkflowCommandAdapterInstructions(
4467
+ workflowId,
4468
+ root,
4469
+ "GitHub Copilot",
4470
+ "prompt"
4471
+ )
4472
+ );
4473
+ };
4474
+ var renderTruthmarkGeminiStructureCommand = (config = defaultAgentConfig()) => {
4475
+ void config;
4476
+ return renderGeminiWorkflowCommand(
4477
+ "truthmark-structure",
4478
+ ".gemini/skills/truthmark-structure"
4479
+ );
4480
+ };
4481
+ var renderTruthmarkGeminiDocumentCommand = (config = defaultAgentConfig()) => {
4482
+ void config;
4483
+ return renderGeminiWorkflowCommand(
4484
+ "truthmark-document",
4485
+ ".gemini/skills/truthmark-document"
4162
4486
  );
4163
4487
  };
4164
4488
  var renderTruthmarkGeminiSyncCommand = (config = defaultAgentConfig()) => {
4165
- const workflow = getTruthmarkWorkflow("truthmark-sync");
4166
- return renderGeminiCommand(
4167
- workflow.description,
4168
- renderTruthSyncSkillBody(config)
4489
+ void config;
4490
+ return renderGeminiWorkflowCommand(
4491
+ "truthmark-sync",
4492
+ ".gemini/skills/truthmark-sync"
4169
4493
  );
4170
4494
  };
4171
4495
  var renderTruthmarkGeminiRealizeCommand = (config = defaultAgentConfig()) => {
4172
- const workflow = getTruthmarkWorkflow("truthmark-realize");
4173
- return renderGeminiCommand(
4174
- workflow.description,
4175
- renderTruthmarkRealizeSkillBody(config)
4496
+ void config;
4497
+ return renderGeminiWorkflowCommand(
4498
+ "truthmark-realize",
4499
+ ".gemini/skills/truthmark-realize"
4176
4500
  );
4177
4501
  };
4178
4502
  var renderTruthmarkGeminiCheckCommand = (config = defaultAgentConfig()) => {
4179
- const workflow = getTruthmarkWorkflow("truthmark-check");
4180
- return renderGeminiCommand(
4181
- workflow.description,
4182
- renderTruthCheckSkillBody(config)
4503
+ void config;
4504
+ return renderGeminiWorkflowCommand(
4505
+ "truthmark-check",
4506
+ ".gemini/skills/truthmark-check"
4183
4507
  );
4184
4508
  };
4185
4509
  var renderTruthmarkGeminiPreviewCommand = (config = defaultAgentConfig()) => {
4186
- const workflow = getTruthmarkWorkflow("truthmark-preview");
4187
- return renderGeminiCommand(
4188
- workflow.description,
4189
- renderTruthPreviewSkillBody(config)
4510
+ void config;
4511
+ return renderGeminiWorkflowCommand(
4512
+ "truthmark-preview",
4513
+ ".gemini/skills/truthmark-preview"
4190
4514
  );
4191
4515
  };
4192
4516
  var renderTruthmarkGeminiPortalCommand = (config = defaultAgentConfig()) => {
4193
- const workflow = getTruthmarkWorkflow("truthmark-portal");
4194
- return renderGeminiCommand(
4195
- workflow.description,
4196
- renderTruthmarkPortalSkillBody(config)
4517
+ void config;
4518
+ return renderGeminiWorkflowCommand(
4519
+ "truthmark-portal",
4520
+ ".gemini/skills/truthmark-portal"
4197
4521
  );
4198
4522
  };
4199
4523
  var renderTruthmarkCopilotStructurePrompt = (config = defaultAgentConfig()) => {
4200
- const workflow = getTruthmarkWorkflow("truthmark-structure");
4201
- return renderCopilotPromptFile(
4202
- workflow.description,
4203
- renderTruthStructureSkillBody(config, {
4204
- includeCopilotCustomAgentMode: true
4205
- })
4524
+ void config;
4525
+ return renderCopilotWorkflowPrompt(
4526
+ "truthmark-structure",
4527
+ ".github/skills/truthmark-structure"
4206
4528
  );
4207
4529
  };
4208
4530
  var renderTruthmarkCopilotDocumentPrompt = (config = defaultAgentConfig()) => {
4209
- const workflow = getTruthmarkWorkflow("truthmark-document");
4210
- return renderCopilotPromptFile(
4211
- workflow.description,
4212
- renderTruthDocumentSkillBody(config, {
4213
- includeCopilotCustomAgentMode: true
4214
- })
4531
+ void config;
4532
+ return renderCopilotWorkflowPrompt(
4533
+ "truthmark-document",
4534
+ ".github/skills/truthmark-document"
4215
4535
  );
4216
4536
  };
4217
4537
  var renderTruthmarkCopilotSyncPrompt = (config = defaultAgentConfig()) => {
4218
- const workflow = getTruthmarkWorkflow("truthmark-sync");
4219
- return renderCopilotPromptFile(
4220
- workflow.description,
4221
- renderTruthSyncSkillBody(config, {
4222
- includeCopilotCustomAgentMode: true
4223
- })
4538
+ void config;
4539
+ return renderCopilotWorkflowPrompt(
4540
+ "truthmark-sync",
4541
+ ".github/skills/truthmark-sync"
4224
4542
  );
4225
4543
  };
4226
4544
  var renderTruthmarkCopilotRealizePrompt = (config = defaultAgentConfig()) => {
4227
- const workflow = getTruthmarkWorkflow("truthmark-realize");
4228
- return renderCopilotPromptFile(
4229
- workflow.description,
4230
- renderTruthmarkRealizeSkillBody(config)
4545
+ void config;
4546
+ return renderCopilotWorkflowPrompt(
4547
+ "truthmark-realize",
4548
+ ".github/skills/truthmark-realize"
4231
4549
  );
4232
4550
  };
4233
4551
  var renderTruthmarkCopilotCheckPrompt = (config = defaultAgentConfig()) => {
4234
- const workflow = getTruthmarkWorkflow("truthmark-check");
4235
- return renderCopilotPromptFile(
4236
- workflow.description,
4237
- renderTruthCheckSkillBody(config, {
4238
- includeCopilotCustomAgentMode: true
4239
- })
4552
+ void config;
4553
+ return renderCopilotWorkflowPrompt(
4554
+ "truthmark-check",
4555
+ ".github/skills/truthmark-check"
4240
4556
  );
4241
4557
  };
4242
4558
  var renderTruthmarkCopilotPreviewPrompt = (config = defaultAgentConfig()) => {
4243
- const workflow = getTruthmarkWorkflow("truthmark-preview");
4244
- return renderCopilotPromptFile(
4245
- workflow.description,
4246
- renderTruthPreviewSkillBody(config)
4559
+ void config;
4560
+ return renderCopilotWorkflowPrompt(
4561
+ "truthmark-preview",
4562
+ ".github/skills/truthmark-preview"
4247
4563
  );
4248
4564
  };
4249
4565
  var renderTruthmarkCopilotPortalPrompt = (config = defaultAgentConfig()) => {
4250
- const workflow = getTruthmarkWorkflow("truthmark-portal");
4251
- return renderCopilotPromptFile(
4252
- workflow.description,
4253
- renderTruthmarkPortalSkillBody(config)
4566
+ void config;
4567
+ return renderCopilotWorkflowPrompt(
4568
+ "truthmark-portal",
4569
+ ".github/skills/truthmark-portal"
4254
4570
  );
4255
4571
  };
4256
4572
 
@@ -4678,8 +4994,8 @@ var geminiFiles = (config, block) => {
4678
4994
  return files;
4679
4995
  };
4680
4996
  var instructionBlockFiles = (paths, block) => {
4681
- return paths.map((path13) => ({
4682
- path: path13,
4997
+ return paths.map((path12) => ({
4998
+ path: path12,
4683
4999
  content: block,
4684
5000
  managedBlock: true
4685
5001
  }));
@@ -4803,16 +5119,16 @@ var upsertManagedBlock = (existingContent, block) => {
4803
5119
 
4804
5120
  ${block}`;
4805
5121
  };
4806
- var writeManagedAgentsFile = async (rootDir, path13 = "AGENTS.md", block) => {
5122
+ var writeManagedAgentsFile = async (rootDir, path12 = "AGENTS.md", block) => {
4807
5123
  let existingContent = null;
4808
5124
  try {
4809
- existingContent = await fs7.readFile(resolveRepoPath(rootDir, path13), "utf8");
5125
+ existingContent = await fs7.readFile(resolveRepoPath(rootDir, path12), "utf8");
4810
5126
  } catch (error) {
4811
5127
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
4812
5128
  throw error;
4813
5129
  }
4814
5130
  }
4815
- return writeRepoFile(rootDir, path13, upsertManagedBlock(existingContent, block));
5131
+ return writeRepoFile(rootDir, path12, upsertManagedBlock(existingContent, block));
4816
5132
  };
4817
5133
  var diagnosticCategoryForPath = (filePath, config) => {
4818
5134
  if (filePath === "AGENTS.md") {
@@ -5078,8 +5394,53 @@ var checkAuthority = async (rootDir, config) => {
5078
5394
  // src/checks/frontmatter.ts
5079
5395
  import fs10 from "fs/promises";
5080
5396
 
5397
+ // src/markdown/frontmatter.ts
5398
+ import { parse as parse3 } from "yaml";
5399
+ var openingDelimiterPattern = /^(?:\uFEFF)?---[ \t]*(?:\r?\n|$)/u;
5400
+ var closingDelimiterPattern = /^---[ \t]*(?:\r?\n|$)/mu;
5401
+ var asRecord = (value) => {
5402
+ if (value && typeof value === "object" && !Array.isArray(value)) {
5403
+ return value;
5404
+ }
5405
+ return {};
5406
+ };
5407
+ var parseFrontmatter = (source, options = {}) => {
5408
+ const openingMatch = openingDelimiterPattern.exec(source);
5409
+ if (!openingMatch) {
5410
+ return {
5411
+ data: {},
5412
+ content: source.replace(/^\uFEFF/u, "")
5413
+ };
5414
+ }
5415
+ const bodyStart = openingMatch[0].length;
5416
+ const bodyAndContent = source.slice(bodyStart);
5417
+ const closingMatch = closingDelimiterPattern.exec(bodyAndContent);
5418
+ if (!closingMatch || typeof closingMatch.index !== "number") {
5419
+ return {
5420
+ data: {},
5421
+ content: source.replace(/^\uFEFF/u, "")
5422
+ };
5423
+ }
5424
+ const yamlSource = bodyAndContent.slice(0, closingMatch.index);
5425
+ const content = bodyAndContent.slice(closingMatch.index + closingMatch[0].length);
5426
+ let data = {};
5427
+ if (yamlSource.trim().length > 0) {
5428
+ try {
5429
+ data = asRecord(parse3(yamlSource));
5430
+ } catch (error) {
5431
+ if (options.throwOnInvalid) {
5432
+ throw error;
5433
+ }
5434
+ data = {};
5435
+ }
5436
+ }
5437
+ return {
5438
+ data,
5439
+ content
5440
+ };
5441
+ };
5442
+
5081
5443
  // src/markdown/parse.ts
5082
- import matter from "gray-matter";
5083
5444
  import { unified } from "unified";
5084
5445
  import remarkParse from "remark-parse";
5085
5446
  import { visit } from "unist-util-visit";
@@ -5093,7 +5454,7 @@ var isInternalLink = (url) => {
5093
5454
  return url.startsWith("#") || !url.includes("://") && !url.startsWith("mailto:");
5094
5455
  };
5095
5456
  var parseMarkdownDocument = (source) => {
5096
- const parsed = matter(source);
5457
+ const parsed = parseFrontmatter(source);
5097
5458
  const tree = unified().use(remarkParse).parse(parsed.content);
5098
5459
  const headings = [];
5099
5460
  const internalLinks = [];
@@ -5117,6 +5478,11 @@ var parseMarkdownDocument = (source) => {
5117
5478
 
5118
5479
  // src/checks/frontmatter.ts
5119
5480
  var isTruthDocumentKind2 = (value) => TRUTH_DOCUMENT_KINDS.includes(value);
5481
+ var ROUTE_RELATIONSHIP_FRONTMATTER_FIELDS = [
5482
+ "realized_by",
5483
+ "realizes",
5484
+ "depends_on"
5485
+ ];
5120
5486
  var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntries = []) => {
5121
5487
  const diagnostics = [];
5122
5488
  const truthDocumentMap = new Map(
@@ -5131,6 +5497,7 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
5131
5497
  const source = await fs10.readFile(absolutePath, "utf8");
5132
5498
  let document;
5133
5499
  try {
5500
+ parseFrontmatter(source, { throwOnInvalid: true });
5134
5501
  document = parseMarkdownDocument(source);
5135
5502
  } catch (error) {
5136
5503
  diagnostics.push({
@@ -5163,6 +5530,28 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
5163
5530
  }
5164
5531
  const routedTruthDocument = truthDocumentMap.get(markdownPath);
5165
5532
  const truthKind = document.frontmatter.truth_kind;
5533
+ const truthLane = document.frontmatter.truth_lane;
5534
+ const isTruthDocument = routedTruthDocument !== void 0 || truthKind !== void 0;
5535
+ if (isTruthDocument) {
5536
+ for (const field of ROUTE_RELATIONSHIP_FRONTMATTER_FIELDS) {
5537
+ if (field in document.frontmatter) {
5538
+ diagnostics.push({
5539
+ category: "frontmatter",
5540
+ severity: "error",
5541
+ message: `Frontmatter field ${field} is not allowed on truth documents; author relationship metadata in fenced route YAML entries instead.`,
5542
+ file: markdownPath
5543
+ });
5544
+ }
5545
+ }
5546
+ }
5547
+ if (truthLane !== void 0 && truthLane !== "product" && truthLane !== "engineering") {
5548
+ diagnostics.push({
5549
+ category: "frontmatter",
5550
+ severity: "error",
5551
+ message: "Frontmatter truth_lane must be product or engineering.",
5552
+ file: markdownPath
5553
+ });
5554
+ }
5166
5555
  if (truthKind !== void 0) {
5167
5556
  if (typeof truthKind !== "string" || !isTruthDocumentKind2(truthKind)) {
5168
5557
  diagnostics.push({
@@ -5181,6 +5570,14 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
5181
5570
  file: markdownPath
5182
5571
  });
5183
5572
  }
5573
+ if (typeof truthKind === "string" && isTruthDocumentKind2(truthKind) && truthLane !== void 0 && (truthLane === "product" || truthLane === "engineering") && truthLane !== laneForTruthDocumentKind(truthKind)) {
5574
+ diagnostics.push({
5575
+ category: "frontmatter",
5576
+ severity: "error",
5577
+ message: `Frontmatter truth_lane ${truthLane} must match truth_kind ${truthKind}.`,
5578
+ file: markdownPath
5579
+ });
5580
+ }
5184
5581
  }
5185
5582
  }
5186
5583
  return diagnostics;
@@ -5340,7 +5737,9 @@ var resolveAreaRouting = async (rootDir, config) => {
5340
5737
  };
5341
5738
  }
5342
5739
  const rootParsed = parseAreasMarkdown(rootRead.source ?? "", {
5343
- truthDocsRoot: config.truthDocsRoot
5740
+ truthDocsRoot: config.truthDocsRoot,
5741
+ productTruthRoot: config.productTruthRoot,
5742
+ engineeringTruthRoot: config.engineeringTruthRoot
5344
5743
  });
5345
5744
  diagnostics.push(
5346
5745
  ...rootParsed.diagnostics.map((diagnostic) => ({
@@ -5379,7 +5778,9 @@ var resolveAreaRouting = async (rootDir, config) => {
5379
5778
  }
5380
5779
  routeFiles.push(areaFile);
5381
5780
  const childParsed = parseAreasMarkdown(childRead.source ?? "", {
5382
- truthDocsRoot: config.truthDocsRoot
5781
+ truthDocsRoot: config.truthDocsRoot,
5782
+ productTruthRoot: config.productTruthRoot,
5783
+ engineeringTruthRoot: config.engineeringTruthRoot
5383
5784
  });
5384
5785
  diagnostics.push(
5385
5786
  ...childParsed.diagnostics.map((diagnostic) => ({
@@ -5656,11 +6057,115 @@ var BROAD_CODE_SURFACES = /* @__PURE__ */ new Set([
5656
6057
  var isBroadCodeSurface = (pattern) => {
5657
6058
  return BROAD_CODE_SURFACES.has(pattern.replace(/\/\*\*\/\*$/u, "/**"));
5658
6059
  };
6060
+ var PRODUCT_LINK_REVIEW_KINDS = /* @__PURE__ */ new Set([
6061
+ "engineering-behavior",
6062
+ "engineering-workflow",
6063
+ "engineering-contract"
6064
+ ]);
6065
+ var normalizeRoot = (value) => value.replaceAll("\\", "/").replace(/\/+$/u, "");
6066
+ var validateLaneShape = (entry, config, areaName) => {
6067
+ const diagnostics = [];
6068
+ const productRoot = normalizeRoot(resolveProductTruthRoot(config));
6069
+ const engineeringRoot = normalizeRoot(resolveEngineeringTruthRoot(config));
6070
+ const normalizedPath = entry.path.replaceAll("\\", "/");
6071
+ const expectedLane = laneForTruthDocumentKind(entry.kind);
6072
+ if (entry.lane !== expectedLane) {
6073
+ diagnostics.push({
6074
+ category: "lane-shape",
6075
+ severity: "error",
6076
+ message: `Truth document ${entry.path} declares ${entry.kind} in ${entry.lane} lane; ${entry.kind} belongs to the ${expectedLane} lane.`,
6077
+ area: areaName,
6078
+ file: entry.path
6079
+ });
6080
+ }
6081
+ if (entry.lane === "product" && !normalizedPath.startsWith(`${productRoot}/`)) {
6082
+ diagnostics.push({
6083
+ category: "lane-shape",
6084
+ severity: "error",
6085
+ message: `Product truth document ${entry.path} must live under ${productRoot}.`,
6086
+ area: areaName,
6087
+ file: entry.path
6088
+ });
6089
+ }
6090
+ if (entry.lane === "engineering" && !normalizedPath.startsWith(`${engineeringRoot}/`)) {
6091
+ diagnostics.push({
6092
+ category: "lane-shape",
6093
+ severity: "error",
6094
+ message: `Engineering truth document ${entry.path} must live under ${engineeringRoot}.`,
6095
+ area: areaName,
6096
+ file: entry.path
6097
+ });
6098
+ }
6099
+ return diagnostics;
6100
+ };
6101
+ var validateRelationshipTargets = (entries) => {
6102
+ const diagnostics = [];
6103
+ const byPath = new Map(entries.map((entry) => [entry.path, entry]));
6104
+ for (const entry of entries) {
6105
+ for (const target of entry.realizedBy) {
6106
+ const targetEntry = byPath.get(target);
6107
+ if (!targetEntry) {
6108
+ diagnostics.push({
6109
+ category: "traceability",
6110
+ severity: "error",
6111
+ message: `Product truth document ${entry.path} declares missing engineering realization ${target}.`,
6112
+ file: entry.path
6113
+ });
6114
+ } else if (targetEntry.lane !== "engineering") {
6115
+ diagnostics.push({
6116
+ category: "traceability",
6117
+ severity: "error",
6118
+ message: `Product truth document ${entry.path} realized_by target ${target} must point to engineering truth.`,
6119
+ file: entry.path
6120
+ });
6121
+ }
6122
+ }
6123
+ for (const target of entry.realizes) {
6124
+ const targetEntry = byPath.get(target);
6125
+ if (!targetEntry) {
6126
+ diagnostics.push({
6127
+ category: "traceability",
6128
+ severity: "error",
6129
+ message: `Engineering truth document ${entry.path} declares missing product truth ${target}.`,
6130
+ file: entry.path
6131
+ });
6132
+ } else if (targetEntry.lane !== "product") {
6133
+ diagnostics.push({
6134
+ category: "traceability",
6135
+ severity: "error",
6136
+ message: `Engineering truth document ${entry.path} realizes target ${target} must point to product truth.`,
6137
+ file: entry.path
6138
+ });
6139
+ }
6140
+ }
6141
+ }
6142
+ return diagnostics;
6143
+ };
6144
+ var validateMissingProductLinkReviews = (entries, reportedPaths) => {
6145
+ const diagnostics = [];
6146
+ const hasProductTruth = entries.some((entry) => entry.lane === "product");
6147
+ if (!hasProductTruth) {
6148
+ return diagnostics;
6149
+ }
6150
+ for (const entry of entries) {
6151
+ if (entry.lane === "engineering" && PRODUCT_LINK_REVIEW_KINDS.has(entry.kind) && entry.realizes.length === 0 && !reportedPaths.has(entry.path)) {
6152
+ reportedPaths.add(entry.path);
6153
+ diagnostics.push({
6154
+ category: "traceability",
6155
+ severity: "review",
6156
+ message: `User-visible engineering truth document ${entry.path} should link product truth with realizes when it implements a product capability.`,
6157
+ file: entry.path
6158
+ });
6159
+ }
6160
+ }
6161
+ return diagnostics;
6162
+ };
5659
6163
  var checkAreas = async (rootDir, config) => {
5660
6164
  const routing = await resolveAreaRouting(rootDir, {
5661
6165
  rootIndex: config.truthmark.paths.routesIndex,
5662
6166
  areaFilesRoot: config.truthmark.paths.routeAreasRoot,
5663
- truthDocsRoot: resolveTruthDocsRoot(config)
6167
+ productTruthRoot: resolveProductTruthRoot(config),
6168
+ engineeringTruthRoot: resolveEngineeringTruthRoot(config)
5664
6169
  });
5665
6170
  const discoveredCodeFiles = await fg4([...COVERAGE_SCAN_PATTERNS], {
5666
6171
  cwd: rootDir,
@@ -5676,6 +6181,7 @@ var checkAreas = async (rootDir, config) => {
5676
6181
  const truthDocumentPaths = [];
5677
6182
  const seenTruthDocumentPaths = /* @__PURE__ */ new Set();
5678
6183
  const truthDocumentEntryMap = /* @__PURE__ */ new Map();
6184
+ const reportedMissingProductLinkPaths = /* @__PURE__ */ new Set();
5679
6185
  const areaCoverage = routing.areas.map((area) => ({
5680
6186
  area,
5681
6187
  valid: true,
@@ -5693,6 +6199,7 @@ var checkAreas = async (rootDir, config) => {
5693
6199
  const truthReferences = routing.truthDocumentReferences;
5694
6200
  for (const area of truthReferences) {
5695
6201
  let areaHasTruthDocumentErrors = false;
6202
+ const areaTruthDocumentEntries = [];
5696
6203
  const registerTruthDocumentEntry = (truthDocumentEntry) => {
5697
6204
  const existingEntry = truthDocumentEntryMap.get(truthDocumentEntry.path);
5698
6205
  if (existingEntry && existingEntry.kind !== truthDocumentEntry.kind) {
@@ -5705,16 +6212,39 @@ var checkAreas = async (rootDir, config) => {
5705
6212
  });
5706
6213
  return false;
5707
6214
  }
6215
+ if (existingEntry && existingEntry.lane !== truthDocumentEntry.lane) {
6216
+ diagnostics.push({
6217
+ category: "area-index",
6218
+ severity: "error",
6219
+ message: `Truth document ${truthDocumentEntry.path} is routed with conflicting lanes ${existingEntry.lane} and ${truthDocumentEntry.lane}.`,
6220
+ area: area.name,
6221
+ file: truthDocumentEntry.path
6222
+ });
6223
+ return false;
6224
+ }
5708
6225
  if (!existingEntry) {
5709
6226
  truthDocumentEntryMap.set(truthDocumentEntry.path, truthDocumentEntry);
6227
+ } else {
6228
+ truthDocumentEntryMap.set(
6229
+ truthDocumentEntry.path,
6230
+ mergeTruthDocumentEntryRelationships(
6231
+ existingEntry,
6232
+ truthDocumentEntry
6233
+ )
6234
+ );
5710
6235
  }
6236
+ areaTruthDocumentEntries.push(truthDocumentEntry);
6237
+ diagnostics.push(
6238
+ ...validateLaneShape(truthDocumentEntry, config, area.name)
6239
+ );
5711
6240
  return true;
5712
6241
  };
5713
- for (const truthDocument of area.truthDocuments) {
6242
+ for (const [
6243
+ truthDocumentIndex,
6244
+ truthDocument
6245
+ ] of area.truthDocuments.entries()) {
6246
+ const routedEntry = area.truthDocumentEntries[truthDocumentIndex];
5714
6247
  if (looksLikeGlob2(truthDocument)) {
5715
- const routedGlobEntry = area.truthDocumentEntries.find(
5716
- (entry) => entry.path === truthDocument
5717
- );
5718
6248
  const matches = (await fg4([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
5719
6249
  if (matches.length === 0) {
5720
6250
  diagnostics.push({
@@ -5746,8 +6276,8 @@ var checkAreas = async (rootDir, config) => {
5746
6276
  seenTruthDocumentPaths.add(match);
5747
6277
  truthDocumentPaths.push(match);
5748
6278
  }
5749
- if (routedGlobEntry && !registerTruthDocumentEntry({
5750
- ...routedGlobEntry,
6279
+ if (routedEntry && !registerTruthDocumentEntry({
6280
+ ...routedEntry,
5751
6281
  path: match
5752
6282
  })) {
5753
6283
  areaHasTruthDocumentErrors = true;
@@ -5785,7 +6315,6 @@ var checkAreas = async (rootDir, config) => {
5785
6315
  seenTruthDocumentPaths.add(truthDocument);
5786
6316
  truthDocumentPaths.push(truthDocument);
5787
6317
  }
5788
- const routedEntry = area.truthDocumentEntries.find((entry) => entry.path === truthDocument);
5789
6318
  if (routedEntry && !registerTruthDocumentEntry(routedEntry)) {
5790
6319
  areaHasTruthDocumentErrors = true;
5791
6320
  }
@@ -5800,6 +6329,12 @@ var checkAreas = async (rootDir, config) => {
5800
6329
  matchingArea.valid = false;
5801
6330
  }
5802
6331
  }
6332
+ diagnostics.push(
6333
+ ...validateMissingProductLinkReviews(
6334
+ areaTruthDocumentEntries,
6335
+ reportedMissingProductLinkPaths
6336
+ )
6337
+ );
5803
6338
  }
5804
6339
  for (const entry of areaCoverage) {
5805
6340
  const { area } = entry;
@@ -5826,7 +6361,10 @@ var checkAreas = async (rootDir, config) => {
5826
6361
  let containedMatches = 0;
5827
6362
  for (const match of matches) {
5828
6363
  try {
5829
- await assertRepoContainment(rootDir, resolveRepoPath(rootDir, match));
6364
+ await assertRepoContainment(
6365
+ rootDir,
6366
+ resolveRepoPath(rootDir, match)
6367
+ );
5830
6368
  containedMatches += 1;
5831
6369
  } catch {
5832
6370
  diagnostics.push({
@@ -5898,10 +6436,12 @@ var checkAreas = async (rootDir, config) => {
5898
6436
  const topologyPressureCount = broadAreaCount + diagnostics.filter(
5899
6437
  (diagnostic) => diagnostic.category === "area-index" && diagnostic.severity === "review"
5900
6438
  ).length;
6439
+ const truthDocumentEntries = [...truthDocumentEntryMap.values()];
6440
+ diagnostics.push(...validateRelationshipTargets(truthDocumentEntries));
5901
6441
  return {
5902
6442
  diagnostics,
5903
6443
  truthDocumentPaths,
5904
- truthDocumentEntries: [...truthDocumentEntryMap.values()],
6444
+ truthDocumentEntries,
5905
6445
  routePrecision: {
5906
6446
  leafAreaCount: routing.areas.length,
5907
6447
  broadAreaCount
@@ -5913,7 +6453,38 @@ var checkAreas = async (rootDir, config) => {
5913
6453
  // src/checks/decisions.ts
5914
6454
  import fs14 from "fs/promises";
5915
6455
  import micromatch5 from "micromatch";
5916
- var REQUIRED_DECISION_HEADINGS = ["Scope", "Product Decisions", "Rationale"];
6456
+ var PRODUCT_CAPABILITY_REQUIRED_HEADINGS = [
6457
+ "Capability Promise",
6458
+ "Users And Value",
6459
+ "Capability Scope",
6460
+ "Current Product Behavior",
6461
+ "Acceptance Criteria",
6462
+ "Product Decisions",
6463
+ "Engineering Realization Links",
6464
+ "Non-Goals"
6465
+ ];
6466
+ var ENGINEERING_REQUIRED_HEADINGS = [
6467
+ "Purpose",
6468
+ "Scope",
6469
+ "Current Implementation Behavior",
6470
+ "Source References",
6471
+ "Product Truth Links",
6472
+ "Maintenance Notes"
6473
+ ];
6474
+ var FORBIDDEN_PRODUCT_HEADINGS = [
6475
+ "Execution Flow",
6476
+ "Execution Model",
6477
+ "Generated File Inventory",
6478
+ "CLI Envelope Details",
6479
+ "Data And Control Flow"
6480
+ ];
6481
+ var FORBIDDEN_ENGINEERING_HEADINGS = [
6482
+ "Product Promise",
6483
+ "User / Stakeholder Value",
6484
+ "Product Decisions",
6485
+ "Product Rationale",
6486
+ "Business Boundary"
6487
+ ];
5917
6488
  var isTruthDocumentKind3 = (value) => {
5918
6489
  return TRUTH_DOCUMENT_KINDS.includes(value);
5919
6490
  };
@@ -5921,16 +6492,18 @@ var escapeRegExp2 = (value) => {
5921
6492
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5922
6493
  };
5923
6494
  var hasHeading = (source, heading) => {
5924
- return new RegExp(`^#{2,3}\\s+${escapeRegExp2(heading)}\\s*$`, "mu").test(source);
6495
+ return new RegExp(`^#{2,3}\\s+${escapeRegExp2(heading)}\\s*$`, "mu").test(
6496
+ source
6497
+ );
5925
6498
  };
5926
6499
  var kindSpecificHeadingMessages = (source, kind) => {
5927
6500
  if (kind === null) {
5928
6501
  return [];
5929
6502
  }
5930
- if (kind === "behavior") {
5931
- return hasHeading(source, "Current Behavior") ? [] : ["Current Behavior"];
6503
+ if (kind === "engineering-behavior") {
6504
+ return hasHeading(source, "Current Implementation Behavior") ? [] : ["Current Implementation Behavior"];
5932
6505
  }
5933
- if (kind === "contract") {
6506
+ if (kind === "engineering-contract") {
5934
6507
  const missingMessages = [];
5935
6508
  if (!hasHeading(source, "Contract Surface")) {
5936
6509
  missingMessages.push("Contract Surface");
@@ -5940,10 +6513,10 @@ var kindSpecificHeadingMessages = (source, kind) => {
5940
6513
  }
5941
6514
  return missingMessages;
5942
6515
  }
5943
- if (kind === "architecture") {
6516
+ if (kind === "engineering-architecture") {
5944
6517
  return hasHeading(source, "Boundaries") || hasHeading(source, "Components") ? [] : ["Boundaries or Components"];
5945
6518
  }
5946
- if (kind === "workflow") {
6519
+ if (kind === "engineering-workflow") {
5947
6520
  const missingMessages = [];
5948
6521
  if (!hasHeading(source, "Triggers")) {
5949
6522
  missingMessages.push("Triggers");
@@ -5953,23 +6526,34 @@ var kindSpecificHeadingMessages = (source, kind) => {
5953
6526
  }
5954
6527
  return missingMessages;
5955
6528
  }
5956
- if (kind === "operations") {
6529
+ if (kind === "engineering-operations") {
5957
6530
  return hasHeading(source, "Runtime Topology") || hasHeading(source, "Configuration") ? [] : ["Runtime Topology or Configuration"];
5958
6531
  }
5959
- if (kind === "test-behavior") {
6532
+ if (kind === "engineering-test-behavior") {
5960
6533
  const missingMessages = [];
5961
6534
  if (!hasHeading(source, "Execution Model")) {
5962
6535
  missingMessages.push("Execution Model");
5963
6536
  }
5964
6537
  if (!hasHeading(source, "Fixtures And Data Model") && !hasHeading(source, "Assertions And Invariants")) {
5965
- missingMessages.push("Fixtures And Data Model or Assertions And Invariants");
6538
+ missingMessages.push(
6539
+ "Fixtures And Data Model or Assertions And Invariants"
6540
+ );
5966
6541
  }
5967
6542
  return missingMessages;
5968
6543
  }
5969
6544
  return [];
5970
6545
  };
6546
+ var productRequiredHeadings = (kind) => {
6547
+ if (kind === "product-capability") {
6548
+ return PRODUCT_CAPABILITY_REQUIRED_HEADINGS;
6549
+ }
6550
+ return PRODUCT_CAPABILITY_REQUIRED_HEADINGS;
6551
+ };
5971
6552
  var decisionTruthGlobs = (config) => {
5972
- return [`${resolveTruthDocsRoot(config)}/**/*.md`];
6553
+ return [
6554
+ `${resolveProductTruthRoot(config)}/**/*.md`,
6555
+ `${resolveEngineeringTruthRoot(config)}/**/*.md`
6556
+ ];
5973
6557
  };
5974
6558
  var isDecisionTruthCandidate = (config, filePath) => {
5975
6559
  return !filePath.endsWith("/README.md") && micromatch5.isMatch(filePath, decisionTruthGlobs(config));
@@ -5983,25 +6567,43 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
5983
6567
  (filePath) => truthDocumentMap.has(filePath) || isDecisionTruthCandidate(config, filePath)
5984
6568
  ).sort();
5985
6569
  for (const filePath of candidatePaths) {
5986
- const source = await fs14.readFile(resolveRepoPath(rootDir, filePath), "utf8");
6570
+ const source = await fs14.readFile(
6571
+ resolveRepoPath(rootDir, filePath),
6572
+ "utf8"
6573
+ );
5987
6574
  const document = parseMarkdownDocument(source);
5988
6575
  const routedTruthDocument = truthDocumentMap.get(filePath);
5989
6576
  const frontmatterTruthKind = typeof document.frontmatter.truth_kind === "string" ? document.frontmatter.truth_kind : null;
5990
6577
  const routedTruthKind = routedTruthDocument?.kindSource === "defaulted" ? null : routedTruthDocument?.kind;
5991
6578
  const truthKind = routedTruthKind ?? (frontmatterTruthKind && isTruthDocumentKind3(frontmatterTruthKind) ? frontmatterTruthKind : inferTruthDocumentKindFromPath(filePath));
5992
- const missingHeadings = REQUIRED_DECISION_HEADINGS.filter(
6579
+ const lane = routedTruthDocument?.lane ?? (truthKind ? laneForTruthDocumentKind(truthKind) : filePath.startsWith(resolveProductTruthRoot(config)) ? "product" : "engineering");
6580
+ const requiredHeadings = lane === "product" ? productRequiredHeadings(truthKind) : ENGINEERING_REQUIRED_HEADINGS;
6581
+ const missingHeadings = requiredHeadings.filter(
5993
6582
  (heading) => !hasHeading(source, heading)
5994
6583
  );
5995
- missingHeadings.push(...kindSpecificHeadingMessages(source, truthKind));
5996
- if (missingHeadings.length === 0) {
6584
+ if (lane === "engineering") {
6585
+ missingHeadings.push(...kindSpecificHeadingMessages(source, truthKind));
6586
+ }
6587
+ const forbiddenHeadings = (lane === "product" ? FORBIDDEN_PRODUCT_HEADINGS : FORBIDDEN_ENGINEERING_HEADINGS).filter((heading) => hasHeading(source, heading));
6588
+ if (missingHeadings.length === 0 && forbiddenHeadings.length === 0) {
5997
6589
  continue;
5998
6590
  }
5999
- diagnostics.push({
6000
- category: "doc-structure",
6001
- severity: "review",
6002
- message: `Canonical truth doc ${filePath} should include ${missingHeadings.join(" and ")} section(s). Decisions should live beside current behavior, not in timestamped planning logs.`,
6003
- file: filePath
6004
- });
6591
+ if (missingHeadings.length > 0) {
6592
+ diagnostics.push({
6593
+ category: "doc-structure",
6594
+ severity: "review",
6595
+ message: `Canonical ${lane} truth doc ${filePath} should include ${missingHeadings.join(" and ")} section(s). Product truth says what must be true and why; engineering truth says how the repository currently realizes it.`,
6596
+ file: filePath
6597
+ });
6598
+ }
6599
+ if (forbiddenHeadings.length > 0) {
6600
+ diagnostics.push({
6601
+ category: "lane-drift",
6602
+ severity: "error",
6603
+ message: `Canonical ${lane} truth doc ${filePath} contains wrong-lane section(s): ${forbiddenHeadings.join(", ")}.`,
6604
+ file: filePath
6605
+ });
6606
+ }
6005
6607
  }
6006
6608
  return diagnostics;
6007
6609
  };
@@ -6093,17 +6695,90 @@ var checkGeneratedSurfaces = async (rootDir, config) => {
6093
6695
  import path10 from "path";
6094
6696
  import micromatch7 from "micromatch";
6095
6697
 
6096
- // src/repo-index/build.ts
6097
- import fs18 from "fs/promises";
6098
- import path8 from "path";
6099
-
6100
6698
  // src/repo-index/file-tree.ts
6101
6699
  import fs16 from "fs/promises";
6102
- import path6 from "path";
6700
+ import path7 from "path";
6103
6701
  import { execa as execa2 } from "execa";
6104
6702
  import fg5 from "fast-glob";
6105
- import matter2 from "gray-matter";
6106
6703
  import micromatch6 from "micromatch";
6704
+
6705
+ // src/truth/source-references.ts
6706
+ import path6 from "path";
6707
+ var repoRootPrefixes = [
6708
+ ".codex/",
6709
+ ".github/",
6710
+ ".truthmark/",
6711
+ "docs/",
6712
+ "src/",
6713
+ "tests/"
6714
+ ];
6715
+ var sourceReferencesHeadingPattern = /^##\s+Source References\s*$/imu;
6716
+ var nextSecondLevelHeadingPattern = /^##\s+/imu;
6717
+ var bulletReferencePattern = /^\s*[-*]\s+(.+?)\s*$/u;
6718
+ var markdownLinkPattern = /^\s*\[[^\]]+\]\(([^)]+)\)\s*$/u;
6719
+ var backtickPathPattern = /^\s*`([^`]+)`\s*$/u;
6720
+ var normalizeSourceReferencePath = (truthDocPath, referencePath) => {
6721
+ const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
6722
+ const isRepoRelative = repoRootPrefixes.some(
6723
+ (prefix) => strippedPath.startsWith(prefix)
6724
+ );
6725
+ if (!isRepoRelative && (strippedPath.startsWith(".") || !strippedPath.includes("/"))) {
6726
+ return path6.posix.normalize(
6727
+ path6.posix.join(path6.posix.dirname(truthDocPath), strippedPath)
6728
+ );
6729
+ }
6730
+ return path6.posix.normalize(strippedPath);
6731
+ };
6732
+ var normalizeReferenceText = (value) => {
6733
+ const trimmed = value.trim();
6734
+ const markdownLinkMatch = markdownLinkPattern.exec(trimmed);
6735
+ if (markdownLinkMatch) {
6736
+ return markdownLinkMatch[1]?.trim() ?? "";
6737
+ }
6738
+ const backtickMatch = backtickPathPattern.exec(trimmed);
6739
+ if (backtickMatch) {
6740
+ return backtickMatch[1]?.trim() ?? "";
6741
+ }
6742
+ return trimmed;
6743
+ };
6744
+ var parseSourceReferencesSection = (content) => {
6745
+ const headingMatch = sourceReferencesHeadingPattern.exec(content);
6746
+ if (!headingMatch || typeof headingMatch.index !== "number") {
6747
+ return [];
6748
+ }
6749
+ const sectionStart = headingMatch.index + headingMatch[0].length;
6750
+ const afterHeading = content.slice(sectionStart);
6751
+ const nextHeadingMatch = nextSecondLevelHeadingPattern.exec(afterHeading);
6752
+ const section = nextHeadingMatch ? afterHeading.slice(0, nextHeadingMatch.index) : afterHeading;
6753
+ const references = [];
6754
+ for (const line of section.split("\n")) {
6755
+ const bulletMatch = bulletReferencePattern.exec(line);
6756
+ if (!bulletMatch) {
6757
+ continue;
6758
+ }
6759
+ const reference = normalizeReferenceText(bulletMatch[1] ?? "");
6760
+ if (reference.length > 0 && !reference.startsWith("{{")) {
6761
+ references.push(reference);
6762
+ }
6763
+ }
6764
+ return references;
6765
+ };
6766
+ var parseSourceReferences = (source, truthDocPath) => {
6767
+ const parsed = parseFrontmatter(source);
6768
+ const references = /* @__PURE__ */ new Set();
6769
+ const frontmatterSourceOfTruth = Array.isArray(parsed.data.source_of_truth) ? parsed.data.source_of_truth : [];
6770
+ for (const entry of frontmatterSourceOfTruth) {
6771
+ if (typeof entry === "string") {
6772
+ references.add(normalizeSourceReferencePath(truthDocPath, entry));
6773
+ }
6774
+ }
6775
+ for (const entry of parseSourceReferencesSection(parsed.content)) {
6776
+ references.add(normalizeSourceReferencePath(truthDocPath, entry));
6777
+ }
6778
+ return [...references].sort();
6779
+ };
6780
+
6781
+ // src/repo-index/file-tree.ts
6107
6782
  var languageByExtension = /* @__PURE__ */ new Map([
6108
6783
  [".ts", "typescript"],
6109
6784
  [".tsx", "typescript"],
@@ -6117,12 +6792,8 @@ var languageByExtension = /* @__PURE__ */ new Map([
6117
6792
  [".yaml", "yaml"],
6118
6793
  [".toml", "toml"]
6119
6794
  ]);
6120
- var sourceExtensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
6121
- var isJavaScriptLikePath = (filePath) => {
6122
- return sourceExtensions.has(path6.posix.extname(filePath));
6123
- };
6124
6795
  var isTestPath = (filePath) => {
6125
- return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path6.posix.basename(filePath));
6796
+ return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path7.posix.basename(filePath));
6126
6797
  };
6127
6798
  var fileKind = (filePath, ignore) => {
6128
6799
  const classification = classifyPath(filePath, ignore);
@@ -6148,12 +6819,14 @@ var fileKind = (filePath, ignore) => {
6148
6819
  };
6149
6820
  var targetHintsForTest = (filePath) => {
6150
6821
  const hints = /* @__PURE__ */ new Set();
6151
- const basename = path6.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
6822
+ const basename = path7.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
6152
6823
  if (basename.length > 0) {
6153
6824
  hints.add(basename);
6154
6825
  }
6155
6826
  const segments = filePath.split("/");
6156
- const testRootIndex = segments.findIndex((segment) => segment === "tests" || segment === "__tests__");
6827
+ const testRootIndex = segments.findIndex(
6828
+ (segment) => segment === "tests" || segment === "__tests__"
6829
+ );
6157
6830
  if (testRootIndex >= 0) {
6158
6831
  for (const segment of segments.slice(testRootIndex + 1, -1)) {
6159
6832
  if (segment.length > 0) {
@@ -6165,6 +6838,9 @@ var targetHintsForTest = (filePath) => {
6165
6838
  };
6166
6839
  var defaultIgnore = [".git/**", "node_modules/**", "dist/**", "build/**"];
6167
6840
  var normalizePath2 = (filePath) => filePath.replaceAll("\\", "/").replace(/^\.\/+/u, "");
6841
+ var isTruthDocumentKind4 = (value) => {
6842
+ return typeof value === "string" && TRUTH_DOCUMENT_KINDS.includes(value);
6843
+ };
6168
6844
  var gitDiscoverableFiles = async (rootDir) => {
6169
6845
  const result = await execa2(
6170
6846
  "git",
@@ -6196,7 +6872,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6196
6872
  for (const filePath of discoveredFiles.filter((entry) => !isIgnoredPath(entry, ignore)).sort()) {
6197
6873
  let stat;
6198
6874
  try {
6199
- stat = await fs16.stat(path6.join(rootDir, filePath));
6875
+ stat = await fs16.stat(path7.join(rootDir, filePath));
6200
6876
  } catch (error) {
6201
6877
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
6202
6878
  continue;
@@ -6206,7 +6882,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6206
6882
  if (!stat.isFile()) {
6207
6883
  continue;
6208
6884
  }
6209
- const extension = path6.posix.extname(filePath);
6885
+ const extension = path7.posix.extname(filePath);
6210
6886
  const kind = fileKind(filePath, ignore);
6211
6887
  files.push({
6212
6888
  path: filePath,
@@ -6220,17 +6896,23 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6220
6896
  });
6221
6897
  }
6222
6898
  if (kind === "doc") {
6223
- const source = await fs16.readFile(path6.join(rootDir, filePath), "utf8");
6224
- const parsed = matter2(source);
6899
+ const source = await fs16.readFile(path7.join(rootDir, filePath), "utf8");
6900
+ const parsed = parseFrontmatter(source);
6225
6901
  const markdown = parseMarkdownDocument(parsed.content);
6226
6902
  const title = markdown.headings.find((heading) => heading.depth === 1)?.text ?? null;
6227
- const sourceOfTruth = Array.isArray(parsed.data.source_of_truth) ? parsed.data.source_of_truth.filter((entry) => typeof entry === "string") : [];
6903
+ const sourceOfTruth = parseSourceReferences(source, filePath);
6904
+ const truthKind = typeof parsed.data.truth_kind === "string" ? parsed.data.truth_kind : null;
6905
+ const derivedTruthKind = isTruthDocumentKind4(truthKind) ? truthKind : null;
6228
6906
  docs.push({
6229
6907
  path: filePath,
6230
6908
  title,
6231
- docType: typeof parsed.data.doc_type === "string" ? parsed.data.doc_type : null,
6232
- truthKind: typeof parsed.data.truth_kind === "string" ? parsed.data.truth_kind : null,
6233
- sourceOfTruth: sourceOfTruth.sort()
6909
+ docType: typeof parsed.data.doc_type === "string" ? parsed.data.doc_type : derivedTruthKind ? docTypeForTruthDocumentKind(derivedTruthKind) : null,
6910
+ truthKind,
6911
+ truthLane: typeof parsed.data.truth_lane === "string" ? parsed.data.truth_lane : derivedTruthKind ? laneForTruthDocumentKind(derivedTruthKind) : null,
6912
+ sourceOfTruth: sourceOfTruth.sort(),
6913
+ realizedBy: [],
6914
+ realizes: [],
6915
+ dependsOn: []
6234
6916
  });
6235
6917
  }
6236
6918
  }
@@ -6243,7 +6925,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6243
6925
 
6244
6926
  // src/repo-index/package-metadata.ts
6245
6927
  import fs17 from "fs/promises";
6246
- import path7 from "path";
6928
+ import path8 from "path";
6247
6929
  import fg6 from "fast-glob";
6248
6930
  var packageManagerFor = async (rootDir, packageDir) => {
6249
6931
  const lockfiles = [
@@ -6255,7 +6937,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
6255
6937
  ];
6256
6938
  for (const [lockfile, manager] of lockfiles) {
6257
6939
  try {
6258
- await fs17.access(path7.join(rootDir, packageDir, lockfile));
6940
+ await fs17.access(path8.join(rootDir, packageDir, lockfile));
6259
6941
  return manager;
6260
6942
  } catch {
6261
6943
  continue;
@@ -6272,8 +6954,8 @@ var discoverPackageMetadata = async (rootDir) => {
6272
6954
  });
6273
6955
  const packages = [];
6274
6956
  for (const packageFile of packageFiles.sort()) {
6275
- const packageDir = path7.posix.dirname(packageFile) === "." ? "" : path7.posix.dirname(packageFile);
6276
- const raw = JSON.parse(await fs17.readFile(path7.join(rootDir, packageFile), "utf8"));
6957
+ const packageDir = path8.posix.dirname(packageFile) === "." ? "" : path8.posix.dirname(packageFile);
6958
+ const raw = JSON.parse(await fs17.readFile(path8.join(rootDir, packageFile), "utf8"));
6277
6959
  const scripts = raw.scripts && typeof raw.scripts === "object" ? Object.keys(raw.scripts).sort() : [];
6278
6960
  packages.push({
6279
6961
  path: packageFile,
@@ -6288,6 +6970,21 @@ var discoverPackageMetadata = async (rootDir) => {
6288
6970
  };
6289
6971
 
6290
6972
  // src/repo-index/route-map.ts
6973
+ var mergedEntryMap = (entries) => {
6974
+ const byPath = /* @__PURE__ */ new Map();
6975
+ for (const entry of entries) {
6976
+ const existingEntry = byPath.get(entry.path);
6977
+ if (existingEntry && existingEntry.kind === entry.kind && existingEntry.lane === entry.lane) {
6978
+ byPath.set(
6979
+ entry.path,
6980
+ mergeTruthDocumentEntryRelationships(existingEntry, entry)
6981
+ );
6982
+ } else if (!existingEntry) {
6983
+ byPath.set(entry.path, entry);
6984
+ }
6985
+ }
6986
+ return byPath;
6987
+ };
6291
6988
  var buildRouteMap = async (rootDir) => {
6292
6989
  const loadResult = await loadConfig(rootDir);
6293
6990
  if (!loadResult.config) {
@@ -6300,8 +6997,12 @@ var buildRouteMap = async (rootDir) => {
6300
6997
  const routing = await resolveAreaRouting(rootDir, {
6301
6998
  rootIndex: loadResult.config.truthmark.paths.routesIndex,
6302
6999
  areaFilesRoot: loadResult.config.truthmark.paths.routeAreasRoot,
6303
- truthDocsRoot: resolveTruthDocsRoot(loadResult.config)
7000
+ productTruthRoot: resolveProductTruthRoot(loadResult.config),
7001
+ engineeringTruthRoot: resolveEngineeringTruthRoot(loadResult.config)
6304
7002
  });
7003
+ const mergedTruthDocumentEntries = mergedEntryMap(
7004
+ routing.areas.flatMap((area) => area.truthDocumentEntries)
7005
+ );
6305
7006
  return {
6306
7007
  schemaVersion: "route-map/v0",
6307
7008
  routes: routing.areas.map((area) => ({
@@ -6312,102 +7013,20 @@ var buildRouteMap = async (rootDir) => {
6312
7013
  parentName: area.parentName,
6313
7014
  codeSurface: [...area.codeSurface].sort(),
6314
7015
  truthDocs: [...area.truthDocuments].sort(),
7016
+ truthDocumentEntries: [
7017
+ ...new Map(
7018
+ area.truthDocumentEntries.map((entry) => [
7019
+ entry.path,
7020
+ mergedTruthDocumentEntries.get(entry.path) ?? entry
7021
+ ])
7022
+ ).values()
7023
+ ].sort((left, right) => left.path.localeCompare(right.path)),
6315
7024
  updateTruthWhen: [...area.updateTruthWhen]
6316
7025
  })).sort((left, right) => left.key.localeCompare(right.key)),
6317
7026
  diagnostics: routing.diagnostics
6318
7027
  };
6319
7028
  };
6320
7029
 
6321
- // src/repo-index/typescript-symbols.ts
6322
- import ts from "typescript";
6323
- var sortStrings = (values) => [...new Set(values)].sort();
6324
- var hasExportModifier = (node) => {
6325
- return Boolean(
6326
- ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
6327
- );
6328
- };
6329
- var declarationName = (node) => {
6330
- if (!node.name || !ts.isIdentifier(node.name)) {
6331
- return null;
6332
- }
6333
- return node.name.text;
6334
- };
6335
- var addExport = (exports, publicSymbols, path13, name, kind) => {
6336
- if (!name) {
6337
- return;
6338
- }
6339
- const entry = { path: path13, name, kind };
6340
- exports.push(entry);
6341
- publicSymbols.push(entry);
6342
- };
6343
- var analyzeTypeScriptSource = (path13, source) => {
6344
- const sourceFile = ts.createSourceFile(path13, source, ts.ScriptTarget.Latest, true);
6345
- const imports = [];
6346
- const exports = [];
6347
- const publicSymbols = [];
6348
- for (const statement of sourceFile.statements) {
6349
- if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
6350
- const imported = [];
6351
- const clause = statement.importClause;
6352
- if (clause?.name) {
6353
- imported.push("default");
6354
- }
6355
- if (clause?.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
6356
- imported.push("*");
6357
- }
6358
- if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
6359
- for (const element of clause.namedBindings.elements) {
6360
- imported.push(element.propertyName?.text ?? element.name.text);
6361
- }
6362
- }
6363
- imports.push({
6364
- from: path13,
6365
- specifier: statement.moduleSpecifier.text,
6366
- imported: sortStrings(imported)
6367
- });
6368
- continue;
6369
- }
6370
- if (ts.isExportDeclaration(statement)) {
6371
- if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
6372
- for (const element of statement.exportClause.elements) {
6373
- addExport(exports, publicSymbols, path13, element.name.text, "re-export");
6374
- }
6375
- }
6376
- continue;
6377
- }
6378
- if (ts.isFunctionDeclaration(statement) && hasExportModifier(statement)) {
6379
- addExport(exports, publicSymbols, path13, declarationName(statement), "function");
6380
- continue;
6381
- }
6382
- if (ts.isClassDeclaration(statement) && hasExportModifier(statement)) {
6383
- addExport(exports, publicSymbols, path13, declarationName(statement), "class");
6384
- continue;
6385
- }
6386
- if (ts.isInterfaceDeclaration(statement) && hasExportModifier(statement)) {
6387
- addExport(exports, publicSymbols, path13, declarationName(statement), "interface");
6388
- continue;
6389
- }
6390
- if (ts.isTypeAliasDeclaration(statement) && hasExportModifier(statement)) {
6391
- addExport(exports, publicSymbols, path13, declarationName(statement), "type");
6392
- continue;
6393
- }
6394
- if (ts.isEnumDeclaration(statement) && hasExportModifier(statement)) {
6395
- addExport(exports, publicSymbols, path13, declarationName(statement), "enum");
6396
- continue;
6397
- }
6398
- if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
6399
- for (const declaration of statement.declarationList.declarations) {
6400
- addExport(exports, publicSymbols, path13, declarationName(declaration), "const");
6401
- }
6402
- }
6403
- }
6404
- return {
6405
- imports: imports.sort((left, right) => left.specifier.localeCompare(right.specifier)),
6406
- exports: exports.sort((left, right) => left.name.localeCompare(right.name)),
6407
- publicSymbols: publicSymbols.sort((left, right) => left.name.localeCompare(right.name))
6408
- };
6409
- };
6410
-
6411
7030
  // src/repo-index/build.ts
6412
7031
  var buildRepoIndex = async (cwd) => {
6413
7032
  const repository = await getGitRepository(cwd);
@@ -6420,20 +7039,7 @@ var buildRepoIndex = async (cwd) => {
6420
7039
  discoverRepoFiles(rootDir, ignore),
6421
7040
  buildRouteMap(rootDir)
6422
7041
  ]);
6423
- const imports = [];
6424
- const exports = [];
6425
- const publicSymbols = [];
6426
7042
  diagnostics.push(...routeMap.diagnostics);
6427
- for (const file of fileTree.files) {
6428
- if (!isJavaScriptLikePath(file.path)) {
6429
- continue;
6430
- }
6431
- const source = await fs18.readFile(path8.join(rootDir, file.path), "utf8");
6432
- const analysis = analyzeTypeScriptSource(file.path, source);
6433
- imports.push(...analysis.imports);
6434
- exports.push(...analysis.exports);
6435
- publicSymbols.push(...analysis.publicSymbols);
6436
- }
6437
7043
  return {
6438
7044
  schemaVersion: "repo-index/v0",
6439
7045
  repository: {
@@ -6445,11 +7051,6 @@ var buildRepoIndex = async (cwd) => {
6445
7051
  files: fileTree.files,
6446
7052
  docs: fileTree.docs,
6447
7053
  tests: fileTree.tests,
6448
- imports: imports.sort((left, right) => `${left.from}:${left.specifier}`.localeCompare(`${right.from}:${right.specifier}`)),
6449
- exports: exports.sort((left, right) => `${left.path}:${left.name}`.localeCompare(`${right.path}:${right.name}`)),
6450
- publicSymbols: publicSymbols.sort(
6451
- (left, right) => `${left.path}:${left.name}`.localeCompare(`${right.path}:${right.name}`)
6452
- ),
6453
7054
  routeMap,
6454
7055
  diagnostics
6455
7056
  };
@@ -6459,7 +7060,7 @@ var buildRepoIndex = async (cwd) => {
6459
7060
  import { execa as execa4 } from "execa";
6460
7061
 
6461
7062
  // src/git/changes.ts
6462
- import fs19 from "fs/promises";
7063
+ import fs18 from "fs/promises";
6463
7064
  import path9 from "path";
6464
7065
  import { execa as execa3 } from "execa";
6465
7066
  var normalizePath3 = (filePath) => {
@@ -6471,7 +7072,7 @@ var listChangedPaths = async (cwd, args) => {
6471
7072
  };
6472
7073
  var pathExists4 = async (filePath) => {
6473
7074
  try {
6474
- await fs19.access(filePath);
7075
+ await fs18.access(filePath);
6475
7076
  return true;
6476
7077
  } catch {
6477
7078
  return false;
@@ -6599,17 +7200,9 @@ var getChangedFiles = async (cwd, base) => {
6599
7200
  diagnostics
6600
7201
  };
6601
7202
  };
6602
- var readBaseFile = async (cwd, base, filePath) => {
6603
- const repository = await getGitRepository(cwd);
6604
- const result = await execa4("git", ["show", `${base}:${filePath}`], {
6605
- cwd: repository.worktreePath,
6606
- reject: false
6607
- });
6608
- return (result.exitCode ?? 1) === 0 ? result.stdout : null;
6609
- };
6610
7203
 
6611
7204
  // src/impact/build.ts
6612
- var uniqueSorted = (values) => [...new Set(values)].sort();
7205
+ var uniqueSorted2 = (values) => [...new Set(values)].sort();
6613
7206
  var routeMatchesFile = (route, filePath) => {
6614
7207
  return route.codeSurface.some((pattern) => micromatch7.isMatch(filePath, pattern));
6615
7208
  };
@@ -6624,65 +7217,15 @@ var toImpactRoute = (route) => ({
6624
7217
  truthDocs: [...route.truthDocs].sort(),
6625
7218
  codeSurface: [...route.codeSurface].sort()
6626
7219
  });
6627
- var changedPathSet = (changedFiles) => {
6628
- return new Set(
6629
- changedFiles.flatMap((file) => [file.path, ...file.previousPath ? [file.previousPath] : []])
6630
- );
6631
- };
6632
7220
  var changedFilePaths = (changedFile) => {
6633
7221
  return [changedFile.path, ...changedFile.previousPath ? [changedFile.previousPath] : []];
6634
7222
  };
6635
- var resolveImportPath = (importEdge) => {
6636
- if (!importEdge.specifier.startsWith(".")) {
6637
- return null;
6638
- }
6639
- const basePath = path10.posix.normalize(path10.posix.join(path10.posix.dirname(importEdge.from), importEdge.specifier));
6640
- const withoutExtension = basePath.replace(/\.[cm]?[jt]sx?$/u, "");
6641
- return withoutExtension;
6642
- };
6643
- var importTargetsChangedFile = (importEdge, changedPath) => {
6644
- const resolved = resolveImportPath(importEdge);
6645
- if (!resolved) {
6646
- return false;
6647
- }
6648
- return changedPath.replace(/\.[cm]?[jt]sx?$/u, "") === resolved;
6649
- };
6650
7223
  var pathSegments = (filePath) => filePath.split("/").filter(Boolean);
6651
7224
  var testHintMatchesChangedFile = (hints, changedPath) => {
6652
7225
  const changedBaseName = path10.posix.basename(changedPath);
6653
7226
  const changedSegments = pathSegments(changedPath);
6654
7227
  return hints.some((hint) => changedBaseName.startsWith(hint) || changedSegments.includes(hint));
6655
7228
  };
6656
- var changedSymbolsFor = async (cwd, base, basePath, currentPath, currentExports) => {
6657
- if (!isJavaScriptLikePath(basePath) && !isJavaScriptLikePath(currentPath)) {
6658
- return [];
6659
- }
6660
- const baseSource = await readBaseFile(cwd, base, basePath);
6661
- const baseExports = baseSource ? analyzeTypeScriptSource(basePath, baseSource).exports : [];
6662
- const currentByName = new Map(currentExports.map((entry) => [entry.name, entry]));
6663
- const baseByName = new Map(baseExports.map((entry) => [entry.name, entry]));
6664
- const changes = [];
6665
- if (basePath !== currentPath) {
6666
- for (const entry of currentExports) {
6667
- changes.push({ path: currentPath, name: entry.name, kind: entry.kind, change: "added" });
6668
- }
6669
- for (const entry of baseExports) {
6670
- changes.push({ path: basePath, name: entry.name, kind: entry.kind, change: "removed" });
6671
- }
6672
- return changes;
6673
- }
6674
- for (const [name, entry] of currentByName) {
6675
- if (!baseByName.has(name)) {
6676
- changes.push({ path: currentPath, name, kind: entry.kind, change: "added" });
6677
- }
6678
- }
6679
- for (const [name, entry] of baseByName) {
6680
- if (!currentByName.has(name)) {
6681
- changes.push({ path: basePath, name, kind: entry.kind, change: "removed" });
6682
- }
6683
- }
6684
- return changes;
6685
- };
6686
7229
  var buildImpactSet = async (cwd, options) => {
6687
7230
  const repository = await getGitRepository(cwd);
6688
7231
  const rootDir = repository.worktreePath;
@@ -6697,7 +7240,6 @@ var buildImpactSet = async (cwd, options) => {
6697
7240
  const affectedRoutes = /* @__PURE__ */ new Map();
6698
7241
  const affectedTruthDocs = [];
6699
7242
  const affectedTests = [];
6700
- const changedPublicSymbols = [];
6701
7243
  const knownTestPaths = new Set(repoIndex.tests.map((test) => test.path));
6702
7244
  for (const changedFile of changedFiles) {
6703
7245
  const routeCandidatePaths = changedFilePaths(changedFile);
@@ -6730,60 +7272,15 @@ var buildImpactSet = async (cwd, options) => {
6730
7272
  file: changedFile.path
6731
7273
  });
6732
7274
  }
6733
- const currentExports = repoIndex.exports.filter((entry) => entry.path === changedFile.path);
6734
- changedPublicSymbols.push(
6735
- ...await changedSymbolsFor(
6736
- rootDir,
6737
- options.base,
6738
- changedFile.previousPath ?? changedFile.path,
6739
- changedFile.path,
6740
- currentExports
6741
- )
6742
- );
6743
- }
6744
- const uniqueAffectedTruthDocs = uniqueSorted(affectedTruthDocs);
6745
- const changedPaths = changedPathSet(changedFiles);
6746
- for (const symbol of changedPublicSymbols) {
6747
- if (uniqueAffectedTruthDocs.length === 0) {
6748
- diagnostics.push({
6749
- category: "impact",
6750
- severity: "review",
6751
- message: `Changed public symbol ${symbol.name} in ${symbol.path} has no affected truth document.`,
6752
- file: symbol.path,
6753
- data: {
6754
- symbol: symbol.name,
6755
- change: symbol.change
6756
- }
6757
- });
6758
- continue;
6759
- }
6760
- if (!uniqueAffectedTruthDocs.some((truthDoc) => changedPaths.has(truthDoc))) {
6761
- diagnostics.push({
6762
- category: "impact",
6763
- severity: "review",
6764
- message: `Changed public symbol ${symbol.name} in ${symbol.path} has affected truth docs but none were changed in this impact set.`,
6765
- file: symbol.path,
6766
- data: {
6767
- symbol: symbol.name,
6768
- change: symbol.change,
6769
- affectedTruthDocs: uniqueAffectedTruthDocs
6770
- }
6771
- });
6772
- }
6773
7275
  }
7276
+ const uniqueAffectedTruthDocs = uniqueSorted2(affectedTruthDocs);
6774
7277
  for (const test of repoIndex.tests) {
6775
- const testImports = repoIndex.imports.filter((edge) => edge.from === test.path);
6776
- const importsChangedFile = changedFiles.some(
6777
- (changedFile) => changedFilePaths(changedFile).some(
6778
- (filePath) => testImports.some((importEdge) => importTargetsChangedFile(importEdge, filePath))
6779
- )
6780
- );
6781
7278
  const hintMatchesChangedFile = changedFiles.some(
6782
7279
  (changedFile) => changedFilePaths(changedFile).some(
6783
7280
  (filePath) => testHintMatchesChangedFile(test.targetHints, filePath)
6784
7281
  )
6785
7282
  );
6786
- if (importsChangedFile || hintMatchesChangedFile) {
7283
+ if (hintMatchesChangedFile) {
6787
7284
  affectedTests.push(test.path);
6788
7285
  }
6789
7286
  }
@@ -6796,10 +7293,7 @@ var buildImpactSet = async (cwd, options) => {
6796
7293
  (left, right) => left.key.localeCompare(right.key)
6797
7294
  ),
6798
7295
  affectedTruthDocs: uniqueAffectedTruthDocs,
6799
- affectedTests: uniqueSorted(affectedTests),
6800
- changedPublicSymbols: changedPublicSymbols.sort(
6801
- (left, right) => `${left.path}:${left.name}:${left.change}`.localeCompare(`${right.path}:${right.name}:${right.change}`)
6802
- ),
7296
+ affectedTests: uniqueSorted2(affectedTests),
6803
7297
  diagnostics
6804
7298
  };
6805
7299
  };
@@ -6825,32 +7319,22 @@ var checkFreshness = async (rootDir, _config, _truthDocumentPaths, base) => {
6825
7319
  };
6826
7320
 
6827
7321
  // src/evidence/validate.ts
6828
- import fs21 from "fs/promises";
7322
+ import fs20 from "fs/promises";
6829
7323
  import fg7 from "fast-glob";
6830
7324
 
6831
7325
  // src/evidence/parse.ts
6832
- import fs20 from "fs/promises";
7326
+ import fs19 from "fs/promises";
6833
7327
  import path11 from "path";
6834
- import matter3 from "gray-matter";
6835
- import { parse as parse3 } from "yaml";
7328
+ import { parse as parse4 } from "yaml";
6836
7329
  var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
6837
7330
  var topLevelEvidenceMarkerPattern = /^evidence\s*:/imu;
6838
- var repoRootPrefixes = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
6839
- var normalizeReferencePath = (truthDocPath, referencePath) => {
6840
- const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
6841
- const isRepoRelative = repoRootPrefixes.some((prefix) => strippedPath.startsWith(prefix));
6842
- if (!isRepoRelative && (strippedPath.startsWith(".") || !strippedPath.includes("/"))) {
6843
- return path11.posix.normalize(path11.posix.join(path11.posix.dirname(truthDocPath), strippedPath));
6844
- }
6845
- return path11.posix.normalize(strippedPath);
6846
- };
6847
7331
  var toEvidenceReference = (truthDocPath, raw) => {
6848
7332
  if (!raw || typeof raw !== "object" || !("path" in raw) || typeof raw.path !== "string") {
6849
7333
  return null;
6850
7334
  }
6851
7335
  return {
6852
7336
  truthDocPath,
6853
- path: normalizeReferencePath(truthDocPath, raw.path),
7337
+ path: normalizeSourceReferencePath(truthDocPath, raw.path),
6854
7338
  symbol: "symbol" in raw && typeof raw.symbol === "string" ? raw.symbol : void 0,
6855
7339
  startLine: "start_line" in raw && typeof raw.start_line === "number" ? raw.start_line : void 0,
6856
7340
  endLine: "end_line" in raw && typeof raw.end_line === "number" ? raw.end_line : void 0,
@@ -6859,18 +7343,14 @@ var toEvidenceReference = (truthDocPath, raw) => {
6859
7343
  };
6860
7344
  };
6861
7345
  var parseEvidenceReferences = async (rootDir, truthDocPath) => {
6862
- const source = await fs20.readFile(path11.join(rootDir, truthDocPath), "utf8");
6863
- const parsed = matter3(source);
7346
+ const source = await fs19.readFile(path11.join(rootDir, truthDocPath), "utf8");
7347
+ const parsed = parseFrontmatter(source);
6864
7348
  const references = [];
6865
- const sourceOfTruth = Array.isArray(parsed.data.source_of_truth) ? parsed.data.source_of_truth : [];
6866
- for (const entry of sourceOfTruth) {
6867
- if (typeof entry !== "string") {
6868
- continue;
6869
- }
7349
+ for (const entry of parseSourceReferences(source, truthDocPath)) {
6870
7350
  references.push({
6871
7351
  truthDocPath,
6872
- path: normalizeReferencePath(truthDocPath, entry),
6873
- source: "frontmatter"
7352
+ path: entry,
7353
+ source: "source-references"
6874
7354
  });
6875
7355
  }
6876
7356
  for (const match of parsed.content.matchAll(yamlFencePattern)) {
@@ -6878,7 +7358,7 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
6878
7358
  if (!topLevelEvidenceMarkerPattern.test(yamlBlock)) {
6879
7359
  continue;
6880
7360
  }
6881
- const block = parse3(yamlBlock);
7361
+ const block = parse4(yamlBlock);
6882
7362
  const rawEvidence = block && typeof block === "object" && "evidence" in block ? block.evidence : null;
6883
7363
  if (!Array.isArray(rawEvidence)) {
6884
7364
  continue;
@@ -6896,7 +7376,7 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
6896
7376
  // src/evidence/validate.ts
6897
7377
  var pathExists5 = async (filePath) => {
6898
7378
  try {
6899
- await fs21.access(filePath);
7379
+ await fs20.access(filePath);
6900
7380
  return true;
6901
7381
  } catch {
6902
7382
  return false;
@@ -6934,15 +7414,6 @@ var validateGlob = async (rootDir, reference) => {
6934
7414
  });
6935
7415
  return matches.length > 0 ? null : diagnosticFor(reference, `Referenced file pattern ${reference.path} does not match any file.`);
6936
7416
  };
6937
- var validateSymbol = async (rootDir, reference) => {
6938
- if (!reference.symbol || !isJavaScriptLikePath(reference.path)) {
6939
- return null;
6940
- }
6941
- const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
6942
- const analysis = analyzeTypeScriptSource(reference.path, source);
6943
- const hasSymbol = analysis.publicSymbols.some((symbol) => symbol.name === reference.symbol);
6944
- return hasSymbol ? null : diagnosticFor(reference, `Evidence symbol ${reference.symbol} was not found in ${reference.path}.`);
6945
- };
6946
7417
  var validateHash = async (rootDir, reference) => {
6947
7418
  if (!reference.contentHash) {
6948
7419
  return null;
@@ -6950,7 +7421,7 @@ var validateHash = async (rootDir, reference) => {
6950
7421
  if (!reference.contentHash.startsWith("sha256:")) {
6951
7422
  return diagnosticFor(reference, `Evidence hash for ${reference.path} must use sha256:.`);
6952
7423
  }
6953
- const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7424
+ const source = await fs20.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
6954
7425
  const lines = source.split("\n");
6955
7426
  const startLine = reference.startLine ?? 1;
6956
7427
  const endLine = reference.endLine ?? lines.length;
@@ -6964,7 +7435,7 @@ var validateLineSpan = async (rootDir, reference) => {
6964
7435
  if (reference.startLine === void 0 && reference.endLine === void 0) {
6965
7436
  return null;
6966
7437
  }
6967
- const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7438
+ const source = await fs20.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
6968
7439
  const lines = source.split("\n");
6969
7440
  const startLine = reference.startLine ?? 1;
6970
7441
  const endLine = reference.endLine ?? lines.length;
@@ -6983,10 +7454,6 @@ var validateReference = async (rootDir, reference) => {
6983
7454
  diagnostics.push(diagnosticFor(reference, `Referenced file ${reference.path} does not exist.`));
6984
7455
  return diagnostics;
6985
7456
  }
6986
- const symbolDiagnostic = await validateSymbol(rootDir, reference);
6987
- if (symbolDiagnostic) {
6988
- diagnostics.push(symbolDiagnostic);
6989
- }
6990
7457
  const lineSpanDiagnostic = await validateLineSpan(rootDir, reference);
6991
7458
  if (lineSpanDiagnostic) {
6992
7459
  diagnostics.push(lineSpanDiagnostic);
@@ -7243,190 +7710,6 @@ var runCheck = async (cwd, options = {}) => {
7243
7710
  };
7244
7711
  };
7245
7712
 
7246
- // src/context-pack/build.ts
7247
- import fs22 from "fs/promises";
7248
- import path12 from "path";
7249
- import fg8 from "fast-glob";
7250
- var uniqueSorted2 = (values) => [...new Set(values)].sort();
7251
- var repoRootPrefixes2 = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
7252
- var isGlobReference2 = (referencePath) => /[*?[\]{}()]/u.test(referencePath);
7253
- var normalizeDocReferencePath = (docPath, referencePath) => {
7254
- const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
7255
- if (strippedPath.length === 0 || strippedPath.startsWith("/")) {
7256
- return null;
7257
- }
7258
- const isRepoRelative = repoRootPrefixes2.some((prefix) => strippedPath.startsWith(prefix));
7259
- const normalized = isRepoRelative ? path12.posix.normalize(strippedPath) : path12.posix.normalize(path12.posix.join(path12.posix.dirname(docPath), strippedPath));
7260
- return normalized === ".." || normalized.startsWith("../") ? null : normalized;
7261
- };
7262
- var readIfExists = async (rootDir, filePath) => {
7263
- try {
7264
- return await fs22.readFile(path12.join(rootDir, filePath), "utf8");
7265
- } catch {
7266
- return null;
7267
- }
7268
- };
7269
- var boundContent = (content) => {
7270
- const lines = content.split("\n");
7271
- if (lines.length <= 200) {
7272
- return { content, truncated: false };
7273
- }
7274
- return {
7275
- content: [...lines.slice(0, 80), "...", ...lines.slice(-40)].join("\n"),
7276
- truncated: true
7277
- };
7278
- };
7279
- var boundedContent = (filePath, content, warnings) => {
7280
- const bounded = boundContent(content);
7281
- if (!bounded.truncated) {
7282
- return { path: filePath, content, truncated: false };
7283
- }
7284
- warnings.push({
7285
- category: "context-pack",
7286
- severity: "review",
7287
- message: `Context source file ${filePath} was truncated to fit ContextPack v0 bounds.`,
7288
- file: filePath
7289
- });
7290
- return {
7291
- path: filePath,
7292
- content: bounded.content,
7293
- truncated: true
7294
- };
7295
- };
7296
- var documentsFor = async (rootDir, paths, warnings) => {
7297
- const documents = [];
7298
- for (const filePath of uniqueSorted2(paths)) {
7299
- const content = await readIfExists(rootDir, filePath);
7300
- if (content !== null) {
7301
- const bounded = boundContent(content);
7302
- if (bounded.truncated) {
7303
- warnings.push({
7304
- category: "context-pack",
7305
- severity: "review",
7306
- message: `Context truth doc ${filePath} was truncated to fit ContextPack v0 bounds.`,
7307
- file: filePath
7308
- });
7309
- }
7310
- documents.push({ path: filePath, content: bounded.content, truncated: bounded.truncated });
7311
- }
7312
- }
7313
- return documents;
7314
- };
7315
- var sourceFilesFor = async (rootDir, paths, warnings) => {
7316
- const sourceFiles = [];
7317
- for (const filePath of uniqueSorted2(paths)) {
7318
- const content = await readIfExists(rootDir, filePath);
7319
- if (content !== null) {
7320
- sourceFiles.push(boundedContent(filePath, content, warnings));
7321
- }
7322
- }
7323
- return sourceFiles;
7324
- };
7325
- var sourceOfTruthPathsFor = async (rootDir, docs, truthDocPaths) => {
7326
- const selectedTruthDocs = new Set(truthDocPaths);
7327
- const sourcePaths = [];
7328
- for (const doc of docs) {
7329
- if (!selectedTruthDocs.has(doc.path)) {
7330
- continue;
7331
- }
7332
- for (const referencePath of doc.sourceOfTruth) {
7333
- const normalizedPath = normalizeDocReferencePath(doc.path, referencePath);
7334
- if (!normalizedPath) {
7335
- continue;
7336
- }
7337
- if (isGlobReference2(normalizedPath)) {
7338
- sourcePaths.push(
7339
- ...await fg8(normalizedPath, {
7340
- cwd: rootDir,
7341
- dot: true,
7342
- onlyFiles: true,
7343
- followSymbolicLinks: false
7344
- })
7345
- );
7346
- } else {
7347
- sourcePaths.push(normalizedPath);
7348
- }
7349
- }
7350
- }
7351
- return uniqueSorted2(sourcePaths);
7352
- };
7353
- var writePathsFor = (workflow, routeIndexPath, truthDocs, routes) => {
7354
- if (workflow === "truth-sync") {
7355
- return uniqueSorted2([routeIndexPath, ...truthDocs]);
7356
- }
7357
- if (workflow === "truth-document") {
7358
- return uniqueSorted2([routeIndexPath, ...truthDocs]);
7359
- }
7360
- return uniqueSorted2(routes.flatMap((route) => route.codeSurface));
7361
- };
7362
- var testCommandsFor = (affectedTests) => {
7363
- return affectedTests.length === 0 ? ["npm test"] : [`npm test -- ${affectedTests.join(" ")}`];
7364
- };
7365
- var buildContextPack = async (cwd, options) => {
7366
- const repoIndex = await buildRepoIndex(cwd);
7367
- const rootDir = repoIndex.repository.root;
7368
- const loadResult = await loadConfig(rootDir);
7369
- const config = loadResult.config ?? (loadResult.status === "missing" ? createDefaultConfig() : null);
7370
- const impactSet = options.base ? await buildImpactSet(rootDir, { base: options.base }) : null;
7371
- const routeMap = impactSet ? repoIndex.routeMap : repoIndex.routeMap;
7372
- const warnings = loadResult.status === "invalid" ? [...loadResult.diagnostics] : [];
7373
- const truthDocPaths = impactSet?.affectedTruthDocs ?? (options.workflow === "truth-realize" ? [] : routeMap.routes.flatMap((route) => route.truthDocs));
7374
- const contextRoutes = impactSet?.affectedRoutes ?? (options.workflow === "truth-realize" ? [] : routeMap.routes);
7375
- if (options.workflow === "truth-realize" && !impactSet) {
7376
- warnings.push({
7377
- category: "context-pack",
7378
- severity: "review",
7379
- message: "truth-realize requires --base to derive bounded allowed write paths."
7380
- });
7381
- }
7382
- const sourceOfTruthPaths = await sourceOfTruthPathsFor(rootDir, repoIndex.docs, truthDocPaths);
7383
- const sourceFilePaths = uniqueSorted2([
7384
- ...impactSet?.changedFiles.filter((file) => !file.deleted).map((file) => file.path) ?? [],
7385
- ...sourceOfTruthPaths
7386
- ]);
7387
- return {
7388
- schemaVersion: "context-pack/v0",
7389
- workflow: options.workflow,
7390
- base: options.base ?? null,
7391
- impactSet,
7392
- routeMap,
7393
- allowedWritePaths: config === null ? [] : writePathsFor(
7394
- options.workflow,
7395
- config.truthmark.paths.routesIndex,
7396
- truthDocPaths,
7397
- contextRoutes
7398
- ),
7399
- truthDocs: await documentsFor(rootDir, truthDocPaths, warnings),
7400
- sourceFiles: await sourceFilesFor(rootDir, sourceFilePaths, warnings),
7401
- testCommands: testCommandsFor(impactSet?.affectedTests ?? []),
7402
- warnings
7403
- };
7404
- };
7405
-
7406
- // src/context-pack/render.ts
7407
- var renderContextPackMarkdown = (pack) => {
7408
- const lines = [
7409
- `# Truthmark ContextPack (${pack.workflow})`,
7410
- "",
7411
- `Schema: ${pack.schemaVersion}`,
7412
- `Base: ${pack.base ?? "none"}`,
7413
- "",
7414
- "## Allowed Write Paths",
7415
- ...pack.allowedWritePaths.map((filePath) => `- ${filePath}`),
7416
- "",
7417
- "## Truth Docs",
7418
- ...pack.truthDocs.map((doc) => `- ${doc.path}${doc.truncated ? " (truncated)" : ""}`),
7419
- "",
7420
- "## Source Files",
7421
- ...pack.sourceFiles.map((file) => `- ${file.path}${file.truncated ? " (truncated)" : ""}`),
7422
- "",
7423
- "## Test Commands",
7424
- ...pack.testCommands.map((command) => `- ${command}`)
7425
- ];
7426
- return `${lines.join("\n")}
7427
- `;
7428
- };
7429
-
7430
7713
  // src/workflow-state/build.ts
7431
7714
  import { execa as execa5 } from "execa";
7432
7715
 
@@ -7533,7 +7816,7 @@ var applicabilityFor = (workflow, diagnostics, impactSet) => {
7533
7816
  return { state: isWriteCapable(workflow) ? "blocked" : "not_applicable", reasons };
7534
7817
  }
7535
7818
  if (workflow === "truthmark-sync" && !impactSet) {
7536
- reasons.push("truthmark-sync requires --base to derive bounded truth-doc write paths.");
7819
+ reasons.push("truthmark-sync requires --base to derive changed-file impact before exposing sync write paths.");
7537
7820
  return { state: "blocked", reasons };
7538
7821
  }
7539
7822
  if (workflow === "truthmark-realize" && !impactSet) {
@@ -7555,9 +7838,8 @@ var contextDataFor = (workflow, repoIndex, config, impactSet) => {
7555
7838
  return {};
7556
7839
  }
7557
7840
  const routeFiles = routeFilesFor(repoIndex);
7558
- const truthDocs = uniqueSorted4(
7559
- impactSet?.affectedTruthDocs ?? repoIndex.routeMap.routes.flatMap((route) => route.truthDocs)
7560
- );
7841
+ const indexedTruthDocs = uniqueSorted4(repoIndex.routeMap.routes.flatMap((route) => route.truthDocs));
7842
+ const truthDocs = workflow === "truthmark-sync" ? indexedTruthDocs : uniqueSorted4(impactSet?.affectedTruthDocs ?? indexedTruthDocs);
7561
7843
  return {
7562
7844
  routeIndexPath: config.truthmark.paths.routesIndex,
7563
7845
  routeFiles,
@@ -7576,7 +7858,7 @@ var nextStepsFor = (workflow, applicability, comparisonBase) => {
7576
7858
  }
7577
7859
  if ((workflow === "truthmark-sync" || workflow === "truthmark-realize") && applicability.state === "blocked" && !comparisonBase) {
7578
7860
  return [
7579
- 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."
7861
+ workflow === "truthmark-sync" ? "Rerun with --base <ref> so Truthmark can derive changed-file impact before exposing sync write paths." : "Rerun with --base <ref> so Truthmark can derive bounded allowed code-write paths."
7580
7862
  ];
7581
7863
  }
7582
7864
  return [];
@@ -7612,7 +7894,8 @@ var buildWorkflowState = async (cwd, options) => {
7612
7894
  checks: {
7613
7895
  required: [...manifestEntry.requiredGates],
7614
7896
  recommended: [...manifestEntry.positiveTriggers],
7615
- helpers: helperCommandsFor2(options.workflow)
7897
+ helpers: helperCommandsFor2(options.workflow),
7898
+ affectedTests: impactSet?.affectedTests ?? []
7616
7899
  },
7617
7900
  nextSteps: nextStepsFor(options.workflow, applicability, comparisonBase),
7618
7901
  reportSections: [...manifestEntry.reportSections]
@@ -7620,7 +7903,7 @@ var buildWorkflowState = async (cwd, options) => {
7620
7903
  };
7621
7904
 
7622
7905
  // src/cli/handlers.ts
7623
- import fs23 from "fs/promises";
7906
+ import fs21 from "fs/promises";
7624
7907
 
7625
7908
  // src/agents/workflow-helper-validation.ts
7626
7909
  import { parse as parseYaml2 } from "yaml";
@@ -7982,12 +8265,6 @@ var runImpact = async (options) => {
7982
8265
  }
7983
8266
  };
7984
8267
  };
7985
- var isContextPackWorkflow = (value) => {
7986
- return value === "truth-sync" || value === "truth-document" || value === "truth-realize";
7987
- };
7988
- var isContextMarkdownFormat = (value) => {
7989
- return value === void 0 || value === "markdown";
7990
- };
7991
8268
  var isTruthmarkWorkflowId = (value) => {
7992
8269
  return typeof value === "string" && TRUTHMARK_WORKFLOW_IDS.includes(value);
7993
8270
  };
@@ -8007,7 +8284,7 @@ var invalidWorkflowResult = (command, workflow) => ({
8007
8284
  });
8008
8285
  var readHelperFile = async (filePath, helper) => {
8009
8286
  try {
8010
- return await fs23.readFile(filePath, "utf8");
8287
+ return await fs21.readFile(filePath, "utf8");
8011
8288
  } catch (error) {
8012
8289
  const message = error instanceof Error ? error.message : String(error);
8013
8290
  return { ok: false, helper, errors: [`could not read file: ${message}`] };
@@ -8032,63 +8309,6 @@ var runValidateWriteLease = async (leaseFile, changedFilesFile) => {
8032
8309
  }
8033
8310
  return validateWriteLeaseText(leaseText, changedText);
8034
8311
  };
8035
- var runContext = async (options) => {
8036
- if (!isContextPackWorkflow(options.workflow)) {
8037
- return {
8038
- command: "context",
8039
- summary: "Truthmark context requires a supported --workflow value.",
8040
- diagnostics: [
8041
- {
8042
- category: "context-pack",
8043
- severity: "error",
8044
- message: "truthmark context requires --workflow truth-sync, truth-document, or truth-realize."
8045
- }
8046
- ]
8047
- };
8048
- }
8049
- if (options.format === "json") {
8050
- return {
8051
- command: "context",
8052
- summary: "Truthmark context no longer supports JSON ContextPack output.",
8053
- diagnostics: [
8054
- {
8055
- category: "context-pack",
8056
- severity: "error",
8057
- message: "JSON ContextPack output was removed in v2; use --format markdown."
8058
- }
8059
- ]
8060
- };
8061
- }
8062
- if (!isContextMarkdownFormat(options.format)) {
8063
- return {
8064
- command: "context",
8065
- summary: "Truthmark context requires a supported --format value.",
8066
- diagnostics: [
8067
- {
8068
- category: "context-pack",
8069
- severity: "error",
8070
- message: "truthmark context supports only --format markdown; JSON ContextPack output was removed in v2."
8071
- }
8072
- ]
8073
- };
8074
- }
8075
- const contextPack = await buildContextPack(process.cwd(), {
8076
- workflow: options.workflow,
8077
- base: options.base
8078
- });
8079
- const diagnostics = contextPack.warnings;
8080
- const summary = `Truthmark context generated ${contextPack.workflow} ContextPack with ${diagnostics.length} warnings.`;
8081
- const markdown = renderContextPackMarkdown(contextPack);
8082
- return {
8083
- command: "context",
8084
- summary,
8085
- diagnostics,
8086
- data: {
8087
- markdown,
8088
- summary
8089
- }
8090
- };
8091
- };
8092
8312
  var runWorkflowStatus = async (options) => {
8093
8313
  if (!isTruthmarkWorkflowId(options.workflow)) {
8094
8314
  return invalidWorkflowResult("workflow status", options.workflow);
@@ -8123,14 +8343,6 @@ var writeResult = (result, options) => {
8123
8343
  `);
8124
8344
  markFailedWhenErrorDiagnosticsExist(result);
8125
8345
  };
8126
- var writeContextResult = (result, options) => {
8127
- if (!options.json && typeof result.data?.markdown === "string") {
8128
- process.stdout.write(result.data.markdown);
8129
- markFailedWhenErrorDiagnosticsExist(result);
8130
- return;
8131
- }
8132
- writeResult(result, options);
8133
- };
8134
8346
  var renderValidationHuman = (result) => {
8135
8347
  if (result.ok === true) {
8136
8348
  return [`${result.helper}: ok`, ...result.checks.map((check) => `- ${check}`)].join("\n");
@@ -8175,7 +8387,7 @@ var buildProgram = () => {
8175
8387
  writeResult(await runCheck2({ base: options.base }), options);
8176
8388
  });
8177
8389
  addJsonOption(
8178
- program.command("index").description("Build the deterministic Truthmark repository index.")
8390
+ program.command("index").description("Inspect derived Truthmark workflow routing metadata for the current checkout.")
8179
8391
  ).action(async (options) => {
8180
8392
  writeResult(await runIndex(), options);
8181
8393
  });
@@ -8184,18 +8396,6 @@ var buildProgram = () => {
8184
8396
  ).action(async (options) => {
8185
8397
  writeResult(await runImpact({ base: options.base }), options);
8186
8398
  });
8187
- addJsonOption(
8188
- 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")
8189
- ).action(async (options) => {
8190
- writeContextResult(
8191
- await runContext({
8192
- workflow: options.workflow,
8193
- base: options.base,
8194
- format: options.format
8195
- }),
8196
- options
8197
- );
8198
- });
8199
8399
  const workflow = program.command("workflow").description("Inspect agent-facing Truthmark workflow state.");
8200
8400
  addJsonOption(
8201
8401
  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")