truthmark 2.1.0 → 2.2.0

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 path14 = 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 path14 === "string" ? inferTruthDocumentKindFromPath(path14, options) : null;
625
+ const inferredLane = typeof path14 === "string" ? inferTruthDocumentLaneFromPath(path14, options) : null;
626
+ const normalizedKind = isTruthDocumentKind(kind) ? kind : inferredKind;
627
+ const normalizedLane = lane === "product" || lane === "engineering" ? lane : normalizedKind ? laneForTruthDocumentKind(normalizedKind) : inferredLane;
628
+ if (typeof path14 !== "string" || path14.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: path14.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,11 +2009,51 @@ 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."
@@ -1782,10 +2065,10 @@ var REPOSITORY_INTELLIGENCE_INSTRUCTIONS = [
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"
@@ -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.
@@ -3071,32 +3304,7 @@ Parent post-sync verification:
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
3305
  - verify the updated docs correspond to the reviewed changed-code surface
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,6 +3390,30 @@ description: '${description}'
3182
3390
  ${prompt}
3183
3391
  `;
3184
3392
  };
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
+ };
3185
3417
  var renderTomlString = (value) => {
3186
3418
  return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"')}"`;
3187
3419
  };
@@ -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
- const subagents = renderWorkflowSubagentSupport(workflowId, host);
3501
- 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
- ];
3821
+ const subagents = renderWorkflowSubagentSupport(workflowId, host);
3822
+ const helpers = getTruthmarkWorkflow(workflowId).helpers ?? [];
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 = (path14) => {
3869
+ const normalized = path14.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((path14) => ({
4998
+ path: path14,
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, path14 = "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, path14), "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, path14, upsertManagedBlock(existingContent, block));
4816
5132
  };
4817
5133
  var diagnosticCategoryForPath = (filePath, config) => {
4818
5134
  if (filePath === "AGENTS.md") {
@@ -5117,6 +5433,11 @@ var parseMarkdownDocument = (source) => {
5117
5433
 
5118
5434
  // src/checks/frontmatter.ts
5119
5435
  var isTruthDocumentKind2 = (value) => TRUTH_DOCUMENT_KINDS.includes(value);
5436
+ var ROUTE_RELATIONSHIP_FRONTMATTER_FIELDS = [
5437
+ "realized_by",
5438
+ "realizes",
5439
+ "depends_on"
5440
+ ];
5120
5441
  var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntries = []) => {
5121
5442
  const diagnostics = [];
5122
5443
  const truthDocumentMap = new Map(
@@ -5163,6 +5484,28 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
5163
5484
  }
5164
5485
  const routedTruthDocument = truthDocumentMap.get(markdownPath);
5165
5486
  const truthKind = document.frontmatter.truth_kind;
5487
+ const truthLane = document.frontmatter.truth_lane;
5488
+ const isTruthDocument = routedTruthDocument !== void 0 || truthKind !== void 0;
5489
+ if (isTruthDocument) {
5490
+ for (const field of ROUTE_RELATIONSHIP_FRONTMATTER_FIELDS) {
5491
+ if (field in document.frontmatter) {
5492
+ diagnostics.push({
5493
+ category: "frontmatter",
5494
+ severity: "error",
5495
+ message: `Frontmatter field ${field} is not allowed on truth documents; author relationship metadata in fenced route YAML entries instead.`,
5496
+ file: markdownPath
5497
+ });
5498
+ }
5499
+ }
5500
+ }
5501
+ if (truthLane !== void 0 && truthLane !== "product" && truthLane !== "engineering") {
5502
+ diagnostics.push({
5503
+ category: "frontmatter",
5504
+ severity: "error",
5505
+ message: "Frontmatter truth_lane must be product or engineering.",
5506
+ file: markdownPath
5507
+ });
5508
+ }
5166
5509
  if (truthKind !== void 0) {
5167
5510
  if (typeof truthKind !== "string" || !isTruthDocumentKind2(truthKind)) {
5168
5511
  diagnostics.push({
@@ -5181,6 +5524,14 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
5181
5524
  file: markdownPath
5182
5525
  });
5183
5526
  }
5527
+ if (typeof truthKind === "string" && isTruthDocumentKind2(truthKind) && truthLane !== void 0 && (truthLane === "product" || truthLane === "engineering") && truthLane !== laneForTruthDocumentKind(truthKind)) {
5528
+ diagnostics.push({
5529
+ category: "frontmatter",
5530
+ severity: "error",
5531
+ message: `Frontmatter truth_lane ${truthLane} must match truth_kind ${truthKind}.`,
5532
+ file: markdownPath
5533
+ });
5534
+ }
5184
5535
  }
5185
5536
  }
5186
5537
  return diagnostics;
@@ -5340,7 +5691,9 @@ var resolveAreaRouting = async (rootDir, config) => {
5340
5691
  };
5341
5692
  }
5342
5693
  const rootParsed = parseAreasMarkdown(rootRead.source ?? "", {
5343
- truthDocsRoot: config.truthDocsRoot
5694
+ truthDocsRoot: config.truthDocsRoot,
5695
+ productTruthRoot: config.productTruthRoot,
5696
+ engineeringTruthRoot: config.engineeringTruthRoot
5344
5697
  });
5345
5698
  diagnostics.push(
5346
5699
  ...rootParsed.diagnostics.map((diagnostic) => ({
@@ -5379,7 +5732,9 @@ var resolveAreaRouting = async (rootDir, config) => {
5379
5732
  }
5380
5733
  routeFiles.push(areaFile);
5381
5734
  const childParsed = parseAreasMarkdown(childRead.source ?? "", {
5382
- truthDocsRoot: config.truthDocsRoot
5735
+ truthDocsRoot: config.truthDocsRoot,
5736
+ productTruthRoot: config.productTruthRoot,
5737
+ engineeringTruthRoot: config.engineeringTruthRoot
5383
5738
  });
5384
5739
  diagnostics.push(
5385
5740
  ...childParsed.diagnostics.map((diagnostic) => ({
@@ -5656,11 +6011,115 @@ var BROAD_CODE_SURFACES = /* @__PURE__ */ new Set([
5656
6011
  var isBroadCodeSurface = (pattern) => {
5657
6012
  return BROAD_CODE_SURFACES.has(pattern.replace(/\/\*\*\/\*$/u, "/**"));
5658
6013
  };
6014
+ var PRODUCT_LINK_REVIEW_KINDS = /* @__PURE__ */ new Set([
6015
+ "engineering-behavior",
6016
+ "engineering-workflow",
6017
+ "engineering-contract"
6018
+ ]);
6019
+ var normalizeRoot = (value) => value.replaceAll("\\", "/").replace(/\/+$/u, "");
6020
+ var validateLaneShape = (entry, config, areaName) => {
6021
+ const diagnostics = [];
6022
+ const productRoot = normalizeRoot(resolveProductTruthRoot(config));
6023
+ const engineeringRoot = normalizeRoot(resolveEngineeringTruthRoot(config));
6024
+ const normalizedPath = entry.path.replaceAll("\\", "/");
6025
+ const expectedLane = laneForTruthDocumentKind(entry.kind);
6026
+ if (entry.lane !== expectedLane) {
6027
+ diagnostics.push({
6028
+ category: "lane-shape",
6029
+ severity: "error",
6030
+ message: `Truth document ${entry.path} declares ${entry.kind} in ${entry.lane} lane; ${entry.kind} belongs to the ${expectedLane} lane.`,
6031
+ area: areaName,
6032
+ file: entry.path
6033
+ });
6034
+ }
6035
+ if (entry.lane === "product" && !normalizedPath.startsWith(`${productRoot}/`)) {
6036
+ diagnostics.push({
6037
+ category: "lane-shape",
6038
+ severity: "error",
6039
+ message: `Product truth document ${entry.path} must live under ${productRoot}.`,
6040
+ area: areaName,
6041
+ file: entry.path
6042
+ });
6043
+ }
6044
+ if (entry.lane === "engineering" && !normalizedPath.startsWith(`${engineeringRoot}/`)) {
6045
+ diagnostics.push({
6046
+ category: "lane-shape",
6047
+ severity: "error",
6048
+ message: `Engineering truth document ${entry.path} must live under ${engineeringRoot}.`,
6049
+ area: areaName,
6050
+ file: entry.path
6051
+ });
6052
+ }
6053
+ return diagnostics;
6054
+ };
6055
+ var validateRelationshipTargets = (entries) => {
6056
+ const diagnostics = [];
6057
+ const byPath = new Map(entries.map((entry) => [entry.path, entry]));
6058
+ for (const entry of entries) {
6059
+ for (const target of entry.realizedBy) {
6060
+ const targetEntry = byPath.get(target);
6061
+ if (!targetEntry) {
6062
+ diagnostics.push({
6063
+ category: "traceability",
6064
+ severity: "error",
6065
+ message: `Product truth document ${entry.path} declares missing engineering realization ${target}.`,
6066
+ file: entry.path
6067
+ });
6068
+ } else if (targetEntry.lane !== "engineering") {
6069
+ diagnostics.push({
6070
+ category: "traceability",
6071
+ severity: "error",
6072
+ message: `Product truth document ${entry.path} realized_by target ${target} must point to engineering truth.`,
6073
+ file: entry.path
6074
+ });
6075
+ }
6076
+ }
6077
+ for (const target of entry.realizes) {
6078
+ const targetEntry = byPath.get(target);
6079
+ if (!targetEntry) {
6080
+ diagnostics.push({
6081
+ category: "traceability",
6082
+ severity: "error",
6083
+ message: `Engineering truth document ${entry.path} declares missing product truth ${target}.`,
6084
+ file: entry.path
6085
+ });
6086
+ } else if (targetEntry.lane !== "product") {
6087
+ diagnostics.push({
6088
+ category: "traceability",
6089
+ severity: "error",
6090
+ message: `Engineering truth document ${entry.path} realizes target ${target} must point to product truth.`,
6091
+ file: entry.path
6092
+ });
6093
+ }
6094
+ }
6095
+ }
6096
+ return diagnostics;
6097
+ };
6098
+ var validateMissingProductLinkReviews = (entries, reportedPaths) => {
6099
+ const diagnostics = [];
6100
+ const hasProductTruth = entries.some((entry) => entry.lane === "product");
6101
+ if (!hasProductTruth) {
6102
+ return diagnostics;
6103
+ }
6104
+ for (const entry of entries) {
6105
+ if (entry.lane === "engineering" && PRODUCT_LINK_REVIEW_KINDS.has(entry.kind) && entry.realizes.length === 0 && !reportedPaths.has(entry.path)) {
6106
+ reportedPaths.add(entry.path);
6107
+ diagnostics.push({
6108
+ category: "traceability",
6109
+ severity: "review",
6110
+ message: `User-visible engineering truth document ${entry.path} should link product truth with realizes when it implements a product capability.`,
6111
+ file: entry.path
6112
+ });
6113
+ }
6114
+ }
6115
+ return diagnostics;
6116
+ };
5659
6117
  var checkAreas = async (rootDir, config) => {
5660
6118
  const routing = await resolveAreaRouting(rootDir, {
5661
6119
  rootIndex: config.truthmark.paths.routesIndex,
5662
6120
  areaFilesRoot: config.truthmark.paths.routeAreasRoot,
5663
- truthDocsRoot: resolveTruthDocsRoot(config)
6121
+ productTruthRoot: resolveProductTruthRoot(config),
6122
+ engineeringTruthRoot: resolveEngineeringTruthRoot(config)
5664
6123
  });
5665
6124
  const discoveredCodeFiles = await fg4([...COVERAGE_SCAN_PATTERNS], {
5666
6125
  cwd: rootDir,
@@ -5676,6 +6135,7 @@ var checkAreas = async (rootDir, config) => {
5676
6135
  const truthDocumentPaths = [];
5677
6136
  const seenTruthDocumentPaths = /* @__PURE__ */ new Set();
5678
6137
  const truthDocumentEntryMap = /* @__PURE__ */ new Map();
6138
+ const reportedMissingProductLinkPaths = /* @__PURE__ */ new Set();
5679
6139
  const areaCoverage = routing.areas.map((area) => ({
5680
6140
  area,
5681
6141
  valid: true,
@@ -5693,6 +6153,7 @@ var checkAreas = async (rootDir, config) => {
5693
6153
  const truthReferences = routing.truthDocumentReferences;
5694
6154
  for (const area of truthReferences) {
5695
6155
  let areaHasTruthDocumentErrors = false;
6156
+ const areaTruthDocumentEntries = [];
5696
6157
  const registerTruthDocumentEntry = (truthDocumentEntry) => {
5697
6158
  const existingEntry = truthDocumentEntryMap.get(truthDocumentEntry.path);
5698
6159
  if (existingEntry && existingEntry.kind !== truthDocumentEntry.kind) {
@@ -5705,16 +6166,39 @@ var checkAreas = async (rootDir, config) => {
5705
6166
  });
5706
6167
  return false;
5707
6168
  }
6169
+ if (existingEntry && existingEntry.lane !== truthDocumentEntry.lane) {
6170
+ diagnostics.push({
6171
+ category: "area-index",
6172
+ severity: "error",
6173
+ message: `Truth document ${truthDocumentEntry.path} is routed with conflicting lanes ${existingEntry.lane} and ${truthDocumentEntry.lane}.`,
6174
+ area: area.name,
6175
+ file: truthDocumentEntry.path
6176
+ });
6177
+ return false;
6178
+ }
5708
6179
  if (!existingEntry) {
5709
6180
  truthDocumentEntryMap.set(truthDocumentEntry.path, truthDocumentEntry);
6181
+ } else {
6182
+ truthDocumentEntryMap.set(
6183
+ truthDocumentEntry.path,
6184
+ mergeTruthDocumentEntryRelationships(
6185
+ existingEntry,
6186
+ truthDocumentEntry
6187
+ )
6188
+ );
5710
6189
  }
6190
+ areaTruthDocumentEntries.push(truthDocumentEntry);
6191
+ diagnostics.push(
6192
+ ...validateLaneShape(truthDocumentEntry, config, area.name)
6193
+ );
5711
6194
  return true;
5712
6195
  };
5713
- for (const truthDocument of area.truthDocuments) {
6196
+ for (const [
6197
+ truthDocumentIndex,
6198
+ truthDocument
6199
+ ] of area.truthDocuments.entries()) {
6200
+ const routedEntry = area.truthDocumentEntries[truthDocumentIndex];
5714
6201
  if (looksLikeGlob2(truthDocument)) {
5715
- const routedGlobEntry = area.truthDocumentEntries.find(
5716
- (entry) => entry.path === truthDocument
5717
- );
5718
6202
  const matches = (await fg4([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
5719
6203
  if (matches.length === 0) {
5720
6204
  diagnostics.push({
@@ -5746,8 +6230,8 @@ var checkAreas = async (rootDir, config) => {
5746
6230
  seenTruthDocumentPaths.add(match);
5747
6231
  truthDocumentPaths.push(match);
5748
6232
  }
5749
- if (routedGlobEntry && !registerTruthDocumentEntry({
5750
- ...routedGlobEntry,
6233
+ if (routedEntry && !registerTruthDocumentEntry({
6234
+ ...routedEntry,
5751
6235
  path: match
5752
6236
  })) {
5753
6237
  areaHasTruthDocumentErrors = true;
@@ -5785,7 +6269,6 @@ var checkAreas = async (rootDir, config) => {
5785
6269
  seenTruthDocumentPaths.add(truthDocument);
5786
6270
  truthDocumentPaths.push(truthDocument);
5787
6271
  }
5788
- const routedEntry = area.truthDocumentEntries.find((entry) => entry.path === truthDocument);
5789
6272
  if (routedEntry && !registerTruthDocumentEntry(routedEntry)) {
5790
6273
  areaHasTruthDocumentErrors = true;
5791
6274
  }
@@ -5800,6 +6283,12 @@ var checkAreas = async (rootDir, config) => {
5800
6283
  matchingArea.valid = false;
5801
6284
  }
5802
6285
  }
6286
+ diagnostics.push(
6287
+ ...validateMissingProductLinkReviews(
6288
+ areaTruthDocumentEntries,
6289
+ reportedMissingProductLinkPaths
6290
+ )
6291
+ );
5803
6292
  }
5804
6293
  for (const entry of areaCoverage) {
5805
6294
  const { area } = entry;
@@ -5826,7 +6315,10 @@ var checkAreas = async (rootDir, config) => {
5826
6315
  let containedMatches = 0;
5827
6316
  for (const match of matches) {
5828
6317
  try {
5829
- await assertRepoContainment(rootDir, resolveRepoPath(rootDir, match));
6318
+ await assertRepoContainment(
6319
+ rootDir,
6320
+ resolveRepoPath(rootDir, match)
6321
+ );
5830
6322
  containedMatches += 1;
5831
6323
  } catch {
5832
6324
  diagnostics.push({
@@ -5898,10 +6390,12 @@ var checkAreas = async (rootDir, config) => {
5898
6390
  const topologyPressureCount = broadAreaCount + diagnostics.filter(
5899
6391
  (diagnostic) => diagnostic.category === "area-index" && diagnostic.severity === "review"
5900
6392
  ).length;
6393
+ const truthDocumentEntries = [...truthDocumentEntryMap.values()];
6394
+ diagnostics.push(...validateRelationshipTargets(truthDocumentEntries));
5901
6395
  return {
5902
6396
  diagnostics,
5903
6397
  truthDocumentPaths,
5904
- truthDocumentEntries: [...truthDocumentEntryMap.values()],
6398
+ truthDocumentEntries,
5905
6399
  routePrecision: {
5906
6400
  leafAreaCount: routing.areas.length,
5907
6401
  broadAreaCount
@@ -5913,7 +6407,38 @@ var checkAreas = async (rootDir, config) => {
5913
6407
  // src/checks/decisions.ts
5914
6408
  import fs14 from "fs/promises";
5915
6409
  import micromatch5 from "micromatch";
5916
- var REQUIRED_DECISION_HEADINGS = ["Scope", "Product Decisions", "Rationale"];
6410
+ var PRODUCT_CAPABILITY_REQUIRED_HEADINGS = [
6411
+ "Capability Promise",
6412
+ "Users And Value",
6413
+ "Capability Scope",
6414
+ "Current Product Behavior",
6415
+ "Acceptance Criteria",
6416
+ "Product Decisions",
6417
+ "Engineering Realization Links",
6418
+ "Non-Goals"
6419
+ ];
6420
+ var ENGINEERING_REQUIRED_HEADINGS = [
6421
+ "Purpose",
6422
+ "Scope",
6423
+ "Current Implementation Behavior",
6424
+ "Source References",
6425
+ "Product Truth Links",
6426
+ "Maintenance Notes"
6427
+ ];
6428
+ var FORBIDDEN_PRODUCT_HEADINGS = [
6429
+ "Execution Flow",
6430
+ "Execution Model",
6431
+ "Generated File Inventory",
6432
+ "CLI Envelope Details",
6433
+ "Data And Control Flow"
6434
+ ];
6435
+ var FORBIDDEN_ENGINEERING_HEADINGS = [
6436
+ "Product Promise",
6437
+ "User / Stakeholder Value",
6438
+ "Product Decisions",
6439
+ "Product Rationale",
6440
+ "Business Boundary"
6441
+ ];
5917
6442
  var isTruthDocumentKind3 = (value) => {
5918
6443
  return TRUTH_DOCUMENT_KINDS.includes(value);
5919
6444
  };
@@ -5921,16 +6446,18 @@ var escapeRegExp2 = (value) => {
5921
6446
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5922
6447
  };
5923
6448
  var hasHeading = (source, heading) => {
5924
- return new RegExp(`^#{2,3}\\s+${escapeRegExp2(heading)}\\s*$`, "mu").test(source);
6449
+ return new RegExp(`^#{2,3}\\s+${escapeRegExp2(heading)}\\s*$`, "mu").test(
6450
+ source
6451
+ );
5925
6452
  };
5926
6453
  var kindSpecificHeadingMessages = (source, kind) => {
5927
6454
  if (kind === null) {
5928
6455
  return [];
5929
6456
  }
5930
- if (kind === "behavior") {
5931
- return hasHeading(source, "Current Behavior") ? [] : ["Current Behavior"];
6457
+ if (kind === "engineering-behavior") {
6458
+ return hasHeading(source, "Current Implementation Behavior") ? [] : ["Current Implementation Behavior"];
5932
6459
  }
5933
- if (kind === "contract") {
6460
+ if (kind === "engineering-contract") {
5934
6461
  const missingMessages = [];
5935
6462
  if (!hasHeading(source, "Contract Surface")) {
5936
6463
  missingMessages.push("Contract Surface");
@@ -5940,10 +6467,10 @@ var kindSpecificHeadingMessages = (source, kind) => {
5940
6467
  }
5941
6468
  return missingMessages;
5942
6469
  }
5943
- if (kind === "architecture") {
6470
+ if (kind === "engineering-architecture") {
5944
6471
  return hasHeading(source, "Boundaries") || hasHeading(source, "Components") ? [] : ["Boundaries or Components"];
5945
6472
  }
5946
- if (kind === "workflow") {
6473
+ if (kind === "engineering-workflow") {
5947
6474
  const missingMessages = [];
5948
6475
  if (!hasHeading(source, "Triggers")) {
5949
6476
  missingMessages.push("Triggers");
@@ -5953,23 +6480,34 @@ var kindSpecificHeadingMessages = (source, kind) => {
5953
6480
  }
5954
6481
  return missingMessages;
5955
6482
  }
5956
- if (kind === "operations") {
6483
+ if (kind === "engineering-operations") {
5957
6484
  return hasHeading(source, "Runtime Topology") || hasHeading(source, "Configuration") ? [] : ["Runtime Topology or Configuration"];
5958
6485
  }
5959
- if (kind === "test-behavior") {
6486
+ if (kind === "engineering-test-behavior") {
5960
6487
  const missingMessages = [];
5961
6488
  if (!hasHeading(source, "Execution Model")) {
5962
6489
  missingMessages.push("Execution Model");
5963
6490
  }
5964
6491
  if (!hasHeading(source, "Fixtures And Data Model") && !hasHeading(source, "Assertions And Invariants")) {
5965
- missingMessages.push("Fixtures And Data Model or Assertions And Invariants");
6492
+ missingMessages.push(
6493
+ "Fixtures And Data Model or Assertions And Invariants"
6494
+ );
5966
6495
  }
5967
6496
  return missingMessages;
5968
6497
  }
5969
6498
  return [];
5970
6499
  };
6500
+ var productRequiredHeadings = (kind) => {
6501
+ if (kind === "product-capability") {
6502
+ return PRODUCT_CAPABILITY_REQUIRED_HEADINGS;
6503
+ }
6504
+ return PRODUCT_CAPABILITY_REQUIRED_HEADINGS;
6505
+ };
5971
6506
  var decisionTruthGlobs = (config) => {
5972
- return [`${resolveTruthDocsRoot(config)}/**/*.md`];
6507
+ return [
6508
+ `${resolveProductTruthRoot(config)}/**/*.md`,
6509
+ `${resolveEngineeringTruthRoot(config)}/**/*.md`
6510
+ ];
5973
6511
  };
5974
6512
  var isDecisionTruthCandidate = (config, filePath) => {
5975
6513
  return !filePath.endsWith("/README.md") && micromatch5.isMatch(filePath, decisionTruthGlobs(config));
@@ -5983,25 +6521,43 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
5983
6521
  (filePath) => truthDocumentMap.has(filePath) || isDecisionTruthCandidate(config, filePath)
5984
6522
  ).sort();
5985
6523
  for (const filePath of candidatePaths) {
5986
- const source = await fs14.readFile(resolveRepoPath(rootDir, filePath), "utf8");
6524
+ const source = await fs14.readFile(
6525
+ resolveRepoPath(rootDir, filePath),
6526
+ "utf8"
6527
+ );
5987
6528
  const document = parseMarkdownDocument(source);
5988
6529
  const routedTruthDocument = truthDocumentMap.get(filePath);
5989
6530
  const frontmatterTruthKind = typeof document.frontmatter.truth_kind === "string" ? document.frontmatter.truth_kind : null;
5990
6531
  const routedTruthKind = routedTruthDocument?.kindSource === "defaulted" ? null : routedTruthDocument?.kind;
5991
6532
  const truthKind = routedTruthKind ?? (frontmatterTruthKind && isTruthDocumentKind3(frontmatterTruthKind) ? frontmatterTruthKind : inferTruthDocumentKindFromPath(filePath));
5992
- const missingHeadings = REQUIRED_DECISION_HEADINGS.filter(
6533
+ const lane = routedTruthDocument?.lane ?? (truthKind ? laneForTruthDocumentKind(truthKind) : filePath.startsWith(resolveProductTruthRoot(config)) ? "product" : "engineering");
6534
+ const requiredHeadings = lane === "product" ? productRequiredHeadings(truthKind) : ENGINEERING_REQUIRED_HEADINGS;
6535
+ const missingHeadings = requiredHeadings.filter(
5993
6536
  (heading) => !hasHeading(source, heading)
5994
6537
  );
5995
- missingHeadings.push(...kindSpecificHeadingMessages(source, truthKind));
5996
- if (missingHeadings.length === 0) {
6538
+ if (lane === "engineering") {
6539
+ missingHeadings.push(...kindSpecificHeadingMessages(source, truthKind));
6540
+ }
6541
+ const forbiddenHeadings = (lane === "product" ? FORBIDDEN_PRODUCT_HEADINGS : FORBIDDEN_ENGINEERING_HEADINGS).filter((heading) => hasHeading(source, heading));
6542
+ if (missingHeadings.length === 0 && forbiddenHeadings.length === 0) {
5997
6543
  continue;
5998
6544
  }
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
- });
6545
+ if (missingHeadings.length > 0) {
6546
+ diagnostics.push({
6547
+ category: "doc-structure",
6548
+ severity: "review",
6549
+ 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.`,
6550
+ file: filePath
6551
+ });
6552
+ }
6553
+ if (forbiddenHeadings.length > 0) {
6554
+ diagnostics.push({
6555
+ category: "lane-drift",
6556
+ severity: "error",
6557
+ message: `Canonical ${lane} truth doc ${filePath} contains wrong-lane section(s): ${forbiddenHeadings.join(", ")}.`,
6558
+ file: filePath
6559
+ });
6560
+ }
6005
6561
  }
6006
6562
  return diagnostics;
6007
6563
  };
@@ -6090,20 +6646,99 @@ var checkGeneratedSurfaces = async (rootDir, config) => {
6090
6646
  };
6091
6647
 
6092
6648
  // src/impact/build.ts
6093
- import path10 from "path";
6649
+ import path11 from "path";
6094
6650
  import micromatch7 from "micromatch";
6095
6651
 
6096
6652
  // src/repo-index/build.ts
6097
6653
  import fs18 from "fs/promises";
6098
- import path8 from "path";
6654
+ import path9 from "path";
6099
6655
 
6100
6656
  // src/repo-index/file-tree.ts
6101
6657
  import fs16 from "fs/promises";
6102
- import path6 from "path";
6658
+ import path7 from "path";
6103
6659
  import { execa as execa2 } from "execa";
6104
6660
  import fg5 from "fast-glob";
6105
- import matter2 from "gray-matter";
6661
+ import matter3 from "gray-matter";
6106
6662
  import micromatch6 from "micromatch";
6663
+
6664
+ // src/truth/source-references.ts
6665
+ import path6 from "path";
6666
+ import matter2 from "gray-matter";
6667
+ var repoRootPrefixes = [
6668
+ ".codex/",
6669
+ ".github/",
6670
+ ".truthmark/",
6671
+ "docs/",
6672
+ "src/",
6673
+ "tests/"
6674
+ ];
6675
+ var sourceReferencesHeadingPattern = /^##\s+Source References\s*$/imu;
6676
+ var nextSecondLevelHeadingPattern = /^##\s+/imu;
6677
+ var bulletReferencePattern = /^\s*[-*]\s+(.+?)\s*$/u;
6678
+ var markdownLinkPattern = /^\s*\[[^\]]+\]\(([^)]+)\)\s*$/u;
6679
+ var backtickPathPattern = /^\s*`([^`]+)`\s*$/u;
6680
+ var normalizeSourceReferencePath = (truthDocPath, referencePath) => {
6681
+ const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
6682
+ const isRepoRelative = repoRootPrefixes.some(
6683
+ (prefix) => strippedPath.startsWith(prefix)
6684
+ );
6685
+ if (!isRepoRelative && (strippedPath.startsWith(".") || !strippedPath.includes("/"))) {
6686
+ return path6.posix.normalize(
6687
+ path6.posix.join(path6.posix.dirname(truthDocPath), strippedPath)
6688
+ );
6689
+ }
6690
+ return path6.posix.normalize(strippedPath);
6691
+ };
6692
+ var normalizeReferenceText = (value) => {
6693
+ const trimmed = value.trim();
6694
+ const markdownLinkMatch = markdownLinkPattern.exec(trimmed);
6695
+ if (markdownLinkMatch) {
6696
+ return markdownLinkMatch[1]?.trim() ?? "";
6697
+ }
6698
+ const backtickMatch = backtickPathPattern.exec(trimmed);
6699
+ if (backtickMatch) {
6700
+ return backtickMatch[1]?.trim() ?? "";
6701
+ }
6702
+ return trimmed;
6703
+ };
6704
+ var parseSourceReferencesSection = (content) => {
6705
+ const headingMatch = sourceReferencesHeadingPattern.exec(content);
6706
+ if (!headingMatch || typeof headingMatch.index !== "number") {
6707
+ return [];
6708
+ }
6709
+ const sectionStart = headingMatch.index + headingMatch[0].length;
6710
+ const afterHeading = content.slice(sectionStart);
6711
+ const nextHeadingMatch = nextSecondLevelHeadingPattern.exec(afterHeading);
6712
+ const section = nextHeadingMatch ? afterHeading.slice(0, nextHeadingMatch.index) : afterHeading;
6713
+ const references = [];
6714
+ for (const line of section.split("\n")) {
6715
+ const bulletMatch = bulletReferencePattern.exec(line);
6716
+ if (!bulletMatch) {
6717
+ continue;
6718
+ }
6719
+ const reference = normalizeReferenceText(bulletMatch[1] ?? "");
6720
+ if (reference.length > 0 && !reference.startsWith("{{")) {
6721
+ references.push(reference);
6722
+ }
6723
+ }
6724
+ return references;
6725
+ };
6726
+ var parseSourceReferences = (source, truthDocPath) => {
6727
+ const parsed = matter2(source);
6728
+ const references = /* @__PURE__ */ new Set();
6729
+ const frontmatterSourceOfTruth = Array.isArray(parsed.data.source_of_truth) ? parsed.data.source_of_truth : [];
6730
+ for (const entry of frontmatterSourceOfTruth) {
6731
+ if (typeof entry === "string") {
6732
+ references.add(normalizeSourceReferencePath(truthDocPath, entry));
6733
+ }
6734
+ }
6735
+ for (const entry of parseSourceReferencesSection(parsed.content)) {
6736
+ references.add(normalizeSourceReferencePath(truthDocPath, entry));
6737
+ }
6738
+ return [...references].sort();
6739
+ };
6740
+
6741
+ // src/repo-index/file-tree.ts
6107
6742
  var languageByExtension = /* @__PURE__ */ new Map([
6108
6743
  [".ts", "typescript"],
6109
6744
  [".tsx", "typescript"],
@@ -6117,12 +6752,19 @@ var languageByExtension = /* @__PURE__ */ new Map([
6117
6752
  [".yaml", "yaml"],
6118
6753
  [".toml", "toml"]
6119
6754
  ]);
6120
- var sourceExtensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
6755
+ var sourceExtensions = /* @__PURE__ */ new Set([
6756
+ ".ts",
6757
+ ".tsx",
6758
+ ".js",
6759
+ ".jsx",
6760
+ ".mjs",
6761
+ ".cjs"
6762
+ ]);
6121
6763
  var isJavaScriptLikePath = (filePath) => {
6122
- return sourceExtensions.has(path6.posix.extname(filePath));
6764
+ return sourceExtensions.has(path7.posix.extname(filePath));
6123
6765
  };
6124
6766
  var isTestPath = (filePath) => {
6125
- return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path6.posix.basename(filePath));
6767
+ return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path7.posix.basename(filePath));
6126
6768
  };
6127
6769
  var fileKind = (filePath, ignore) => {
6128
6770
  const classification = classifyPath(filePath, ignore);
@@ -6148,12 +6790,14 @@ var fileKind = (filePath, ignore) => {
6148
6790
  };
6149
6791
  var targetHintsForTest = (filePath) => {
6150
6792
  const hints = /* @__PURE__ */ new Set();
6151
- const basename = path6.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
6793
+ const basename = path7.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
6152
6794
  if (basename.length > 0) {
6153
6795
  hints.add(basename);
6154
6796
  }
6155
6797
  const segments = filePath.split("/");
6156
- const testRootIndex = segments.findIndex((segment) => segment === "tests" || segment === "__tests__");
6798
+ const testRootIndex = segments.findIndex(
6799
+ (segment) => segment === "tests" || segment === "__tests__"
6800
+ );
6157
6801
  if (testRootIndex >= 0) {
6158
6802
  for (const segment of segments.slice(testRootIndex + 1, -1)) {
6159
6803
  if (segment.length > 0) {
@@ -6165,6 +6809,9 @@ var targetHintsForTest = (filePath) => {
6165
6809
  };
6166
6810
  var defaultIgnore = [".git/**", "node_modules/**", "dist/**", "build/**"];
6167
6811
  var normalizePath2 = (filePath) => filePath.replaceAll("\\", "/").replace(/^\.\/+/u, "");
6812
+ var isTruthDocumentKind4 = (value) => {
6813
+ return typeof value === "string" && TRUTH_DOCUMENT_KINDS.includes(value);
6814
+ };
6168
6815
  var gitDiscoverableFiles = async (rootDir) => {
6169
6816
  const result = await execa2(
6170
6817
  "git",
@@ -6196,7 +6843,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6196
6843
  for (const filePath of discoveredFiles.filter((entry) => !isIgnoredPath(entry, ignore)).sort()) {
6197
6844
  let stat;
6198
6845
  try {
6199
- stat = await fs16.stat(path6.join(rootDir, filePath));
6846
+ stat = await fs16.stat(path7.join(rootDir, filePath));
6200
6847
  } catch (error) {
6201
6848
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
6202
6849
  continue;
@@ -6206,7 +6853,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6206
6853
  if (!stat.isFile()) {
6207
6854
  continue;
6208
6855
  }
6209
- const extension = path6.posix.extname(filePath);
6856
+ const extension = path7.posix.extname(filePath);
6210
6857
  const kind = fileKind(filePath, ignore);
6211
6858
  files.push({
6212
6859
  path: filePath,
@@ -6220,17 +6867,23 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6220
6867
  });
6221
6868
  }
6222
6869
  if (kind === "doc") {
6223
- const source = await fs16.readFile(path6.join(rootDir, filePath), "utf8");
6224
- const parsed = matter2(source);
6870
+ const source = await fs16.readFile(path7.join(rootDir, filePath), "utf8");
6871
+ const parsed = matter3(source);
6225
6872
  const markdown = parseMarkdownDocument(parsed.content);
6226
6873
  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") : [];
6874
+ const sourceOfTruth = parseSourceReferences(source, filePath);
6875
+ const truthKind = typeof parsed.data.truth_kind === "string" ? parsed.data.truth_kind : null;
6876
+ const derivedTruthKind = isTruthDocumentKind4(truthKind) ? truthKind : null;
6228
6877
  docs.push({
6229
6878
  path: filePath,
6230
6879
  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()
6880
+ docType: typeof parsed.data.doc_type === "string" ? parsed.data.doc_type : derivedTruthKind ? docTypeForTruthDocumentKind(derivedTruthKind) : null,
6881
+ truthKind,
6882
+ truthLane: typeof parsed.data.truth_lane === "string" ? parsed.data.truth_lane : derivedTruthKind ? laneForTruthDocumentKind(derivedTruthKind) : null,
6883
+ sourceOfTruth: sourceOfTruth.sort(),
6884
+ realizedBy: [],
6885
+ realizes: [],
6886
+ dependsOn: []
6234
6887
  });
6235
6888
  }
6236
6889
  }
@@ -6243,7 +6896,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6243
6896
 
6244
6897
  // src/repo-index/package-metadata.ts
6245
6898
  import fs17 from "fs/promises";
6246
- import path7 from "path";
6899
+ import path8 from "path";
6247
6900
  import fg6 from "fast-glob";
6248
6901
  var packageManagerFor = async (rootDir, packageDir) => {
6249
6902
  const lockfiles = [
@@ -6255,7 +6908,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
6255
6908
  ];
6256
6909
  for (const [lockfile, manager] of lockfiles) {
6257
6910
  try {
6258
- await fs17.access(path7.join(rootDir, packageDir, lockfile));
6911
+ await fs17.access(path8.join(rootDir, packageDir, lockfile));
6259
6912
  return manager;
6260
6913
  } catch {
6261
6914
  continue;
@@ -6272,8 +6925,8 @@ var discoverPackageMetadata = async (rootDir) => {
6272
6925
  });
6273
6926
  const packages = [];
6274
6927
  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"));
6928
+ const packageDir = path8.posix.dirname(packageFile) === "." ? "" : path8.posix.dirname(packageFile);
6929
+ const raw = JSON.parse(await fs17.readFile(path8.join(rootDir, packageFile), "utf8"));
6277
6930
  const scripts = raw.scripts && typeof raw.scripts === "object" ? Object.keys(raw.scripts).sort() : [];
6278
6931
  packages.push({
6279
6932
  path: packageFile,
@@ -6288,6 +6941,21 @@ var discoverPackageMetadata = async (rootDir) => {
6288
6941
  };
6289
6942
 
6290
6943
  // src/repo-index/route-map.ts
6944
+ var mergedEntryMap = (entries) => {
6945
+ const byPath = /* @__PURE__ */ new Map();
6946
+ for (const entry of entries) {
6947
+ const existingEntry = byPath.get(entry.path);
6948
+ if (existingEntry && existingEntry.kind === entry.kind && existingEntry.lane === entry.lane) {
6949
+ byPath.set(
6950
+ entry.path,
6951
+ mergeTruthDocumentEntryRelationships(existingEntry, entry)
6952
+ );
6953
+ } else if (!existingEntry) {
6954
+ byPath.set(entry.path, entry);
6955
+ }
6956
+ }
6957
+ return byPath;
6958
+ };
6291
6959
  var buildRouteMap = async (rootDir) => {
6292
6960
  const loadResult = await loadConfig(rootDir);
6293
6961
  if (!loadResult.config) {
@@ -6300,8 +6968,12 @@ var buildRouteMap = async (rootDir) => {
6300
6968
  const routing = await resolveAreaRouting(rootDir, {
6301
6969
  rootIndex: loadResult.config.truthmark.paths.routesIndex,
6302
6970
  areaFilesRoot: loadResult.config.truthmark.paths.routeAreasRoot,
6303
- truthDocsRoot: resolveTruthDocsRoot(loadResult.config)
6971
+ productTruthRoot: resolveProductTruthRoot(loadResult.config),
6972
+ engineeringTruthRoot: resolveEngineeringTruthRoot(loadResult.config)
6304
6973
  });
6974
+ const mergedTruthDocumentEntries = mergedEntryMap(
6975
+ routing.areas.flatMap((area) => area.truthDocumentEntries)
6976
+ );
6305
6977
  return {
6306
6978
  schemaVersion: "route-map/v0",
6307
6979
  routes: routing.areas.map((area) => ({
@@ -6312,6 +6984,14 @@ var buildRouteMap = async (rootDir) => {
6312
6984
  parentName: area.parentName,
6313
6985
  codeSurface: [...area.codeSurface].sort(),
6314
6986
  truthDocs: [...area.truthDocuments].sort(),
6987
+ truthDocumentEntries: [
6988
+ ...new Map(
6989
+ area.truthDocumentEntries.map((entry) => [
6990
+ entry.path,
6991
+ mergedTruthDocumentEntries.get(entry.path) ?? entry
6992
+ ])
6993
+ ).values()
6994
+ ].sort((left, right) => left.path.localeCompare(right.path)),
6315
6995
  updateTruthWhen: [...area.updateTruthWhen]
6316
6996
  })).sort((left, right) => left.key.localeCompare(right.key)),
6317
6997
  diagnostics: routing.diagnostics
@@ -6332,16 +7012,16 @@ var declarationName = (node) => {
6332
7012
  }
6333
7013
  return node.name.text;
6334
7014
  };
6335
- var addExport = (exports, publicSymbols, path13, name, kind) => {
7015
+ var addExport = (exports, publicSymbols, path14, name, kind) => {
6336
7016
  if (!name) {
6337
7017
  return;
6338
7018
  }
6339
- const entry = { path: path13, name, kind };
7019
+ const entry = { path: path14, name, kind };
6340
7020
  exports.push(entry);
6341
7021
  publicSymbols.push(entry);
6342
7022
  };
6343
- var analyzeTypeScriptSource = (path13, source) => {
6344
- const sourceFile = ts.createSourceFile(path13, source, ts.ScriptTarget.Latest, true);
7023
+ var analyzeTypeScriptSource = (path14, source) => {
7024
+ const sourceFile = ts.createSourceFile(path14, source, ts.ScriptTarget.Latest, true);
6345
7025
  const imports = [];
6346
7026
  const exports = [];
6347
7027
  const publicSymbols = [];
@@ -6361,7 +7041,7 @@ var analyzeTypeScriptSource = (path13, source) => {
6361
7041
  }
6362
7042
  }
6363
7043
  imports.push({
6364
- from: path13,
7044
+ from: path14,
6365
7045
  specifier: statement.moduleSpecifier.text,
6366
7046
  imported: sortStrings(imported)
6367
7047
  });
@@ -6370,34 +7050,34 @@ var analyzeTypeScriptSource = (path13, source) => {
6370
7050
  if (ts.isExportDeclaration(statement)) {
6371
7051
  if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
6372
7052
  for (const element of statement.exportClause.elements) {
6373
- addExport(exports, publicSymbols, path13, element.name.text, "re-export");
7053
+ addExport(exports, publicSymbols, path14, element.name.text, "re-export");
6374
7054
  }
6375
7055
  }
6376
7056
  continue;
6377
7057
  }
6378
7058
  if (ts.isFunctionDeclaration(statement) && hasExportModifier(statement)) {
6379
- addExport(exports, publicSymbols, path13, declarationName(statement), "function");
7059
+ addExport(exports, publicSymbols, path14, declarationName(statement), "function");
6380
7060
  continue;
6381
7061
  }
6382
7062
  if (ts.isClassDeclaration(statement) && hasExportModifier(statement)) {
6383
- addExport(exports, publicSymbols, path13, declarationName(statement), "class");
7063
+ addExport(exports, publicSymbols, path14, declarationName(statement), "class");
6384
7064
  continue;
6385
7065
  }
6386
7066
  if (ts.isInterfaceDeclaration(statement) && hasExportModifier(statement)) {
6387
- addExport(exports, publicSymbols, path13, declarationName(statement), "interface");
7067
+ addExport(exports, publicSymbols, path14, declarationName(statement), "interface");
6388
7068
  continue;
6389
7069
  }
6390
7070
  if (ts.isTypeAliasDeclaration(statement) && hasExportModifier(statement)) {
6391
- addExport(exports, publicSymbols, path13, declarationName(statement), "type");
7071
+ addExport(exports, publicSymbols, path14, declarationName(statement), "type");
6392
7072
  continue;
6393
7073
  }
6394
7074
  if (ts.isEnumDeclaration(statement) && hasExportModifier(statement)) {
6395
- addExport(exports, publicSymbols, path13, declarationName(statement), "enum");
7075
+ addExport(exports, publicSymbols, path14, declarationName(statement), "enum");
6396
7076
  continue;
6397
7077
  }
6398
7078
  if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
6399
7079
  for (const declaration of statement.declarationList.declarations) {
6400
- addExport(exports, publicSymbols, path13, declarationName(declaration), "const");
7080
+ addExport(exports, publicSymbols, path14, declarationName(declaration), "const");
6401
7081
  }
6402
7082
  }
6403
7083
  }
@@ -6428,7 +7108,7 @@ var buildRepoIndex = async (cwd) => {
6428
7108
  if (!isJavaScriptLikePath(file.path)) {
6429
7109
  continue;
6430
7110
  }
6431
- const source = await fs18.readFile(path8.join(rootDir, file.path), "utf8");
7111
+ const source = await fs18.readFile(path9.join(rootDir, file.path), "utf8");
6432
7112
  const analysis = analyzeTypeScriptSource(file.path, source);
6433
7113
  imports.push(...analysis.imports);
6434
7114
  exports.push(...analysis.exports);
@@ -6460,7 +7140,7 @@ import { execa as execa4 } from "execa";
6460
7140
 
6461
7141
  // src/git/changes.ts
6462
7142
  import fs19 from "fs/promises";
6463
- import path9 from "path";
7143
+ import path10 from "path";
6464
7144
  import { execa as execa3 } from "execa";
6465
7145
  var normalizePath3 = (filePath) => {
6466
7146
  return filePath.replaceAll("\\", "/").replace(/^\.\//u, "");
@@ -6515,7 +7195,7 @@ var getUncommittedChanges = async (cwd) => {
6515
7195
  const deletedPathCandidates = /* @__PURE__ */ new Set([...stagedDeletedPaths, ...unstagedDeletedPaths]);
6516
7196
  for (const deletedPath of deletedPathCandidates) {
6517
7197
  const change = getOrCreateChange(changesByPath, deletedPath);
6518
- change.deleted = !await pathExists4(path9.join(rootDir, deletedPath));
7198
+ change.deleted = !await pathExists4(path10.join(rootDir, deletedPath));
6519
7199
  }
6520
7200
  return Array.from(changesByPath.values()).sort((left, right) => {
6521
7201
  return left.path.localeCompare(right.path);
@@ -6609,7 +7289,7 @@ var readBaseFile = async (cwd, base, filePath) => {
6609
7289
  };
6610
7290
 
6611
7291
  // src/impact/build.ts
6612
- var uniqueSorted = (values) => [...new Set(values)].sort();
7292
+ var uniqueSorted2 = (values) => [...new Set(values)].sort();
6613
7293
  var routeMatchesFile = (route, filePath) => {
6614
7294
  return route.codeSurface.some((pattern) => micromatch7.isMatch(filePath, pattern));
6615
7295
  };
@@ -6636,7 +7316,7 @@ var resolveImportPath = (importEdge) => {
6636
7316
  if (!importEdge.specifier.startsWith(".")) {
6637
7317
  return null;
6638
7318
  }
6639
- const basePath = path10.posix.normalize(path10.posix.join(path10.posix.dirname(importEdge.from), importEdge.specifier));
7319
+ const basePath = path11.posix.normalize(path11.posix.join(path11.posix.dirname(importEdge.from), importEdge.specifier));
6640
7320
  const withoutExtension = basePath.replace(/\.[cm]?[jt]sx?$/u, "");
6641
7321
  return withoutExtension;
6642
7322
  };
@@ -6649,7 +7329,7 @@ var importTargetsChangedFile = (importEdge, changedPath) => {
6649
7329
  };
6650
7330
  var pathSegments = (filePath) => filePath.split("/").filter(Boolean);
6651
7331
  var testHintMatchesChangedFile = (hints, changedPath) => {
6652
- const changedBaseName = path10.posix.basename(changedPath);
7332
+ const changedBaseName = path11.posix.basename(changedPath);
6653
7333
  const changedSegments = pathSegments(changedPath);
6654
7334
  return hints.some((hint) => changedBaseName.startsWith(hint) || changedSegments.includes(hint));
6655
7335
  };
@@ -6741,7 +7421,7 @@ var buildImpactSet = async (cwd, options) => {
6741
7421
  )
6742
7422
  );
6743
7423
  }
6744
- const uniqueAffectedTruthDocs = uniqueSorted(affectedTruthDocs);
7424
+ const uniqueAffectedTruthDocs = uniqueSorted2(affectedTruthDocs);
6745
7425
  const changedPaths = changedPathSet(changedFiles);
6746
7426
  for (const symbol of changedPublicSymbols) {
6747
7427
  if (uniqueAffectedTruthDocs.length === 0) {
@@ -6796,7 +7476,7 @@ var buildImpactSet = async (cwd, options) => {
6796
7476
  (left, right) => left.key.localeCompare(right.key)
6797
7477
  ),
6798
7478
  affectedTruthDocs: uniqueAffectedTruthDocs,
6799
- affectedTests: uniqueSorted(affectedTests),
7479
+ affectedTests: uniqueSorted2(affectedTests),
6800
7480
  changedPublicSymbols: changedPublicSymbols.sort(
6801
7481
  (left, right) => `${left.path}:${left.name}:${left.change}`.localeCompare(`${right.path}:${right.name}:${right.change}`)
6802
7482
  ),
@@ -6830,27 +7510,18 @@ import fg7 from "fast-glob";
6830
7510
 
6831
7511
  // src/evidence/parse.ts
6832
7512
  import fs20 from "fs/promises";
6833
- import path11 from "path";
6834
- import matter3 from "gray-matter";
7513
+ import path12 from "path";
7514
+ import matter4 from "gray-matter";
6835
7515
  import { parse as parse3 } from "yaml";
6836
7516
  var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
6837
7517
  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
7518
  var toEvidenceReference = (truthDocPath, raw) => {
6848
7519
  if (!raw || typeof raw !== "object" || !("path" in raw) || typeof raw.path !== "string") {
6849
7520
  return null;
6850
7521
  }
6851
7522
  return {
6852
7523
  truthDocPath,
6853
- path: normalizeReferencePath(truthDocPath, raw.path),
7524
+ path: normalizeSourceReferencePath(truthDocPath, raw.path),
6854
7525
  symbol: "symbol" in raw && typeof raw.symbol === "string" ? raw.symbol : void 0,
6855
7526
  startLine: "start_line" in raw && typeof raw.start_line === "number" ? raw.start_line : void 0,
6856
7527
  endLine: "end_line" in raw && typeof raw.end_line === "number" ? raw.end_line : void 0,
@@ -6859,18 +7530,14 @@ var toEvidenceReference = (truthDocPath, raw) => {
6859
7530
  };
6860
7531
  };
6861
7532
  var parseEvidenceReferences = async (rootDir, truthDocPath) => {
6862
- const source = await fs20.readFile(path11.join(rootDir, truthDocPath), "utf8");
6863
- const parsed = matter3(source);
7533
+ const source = await fs20.readFile(path12.join(rootDir, truthDocPath), "utf8");
7534
+ const parsed = matter4(source);
6864
7535
  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
- }
7536
+ for (const entry of parseSourceReferences(source, truthDocPath)) {
6870
7537
  references.push({
6871
7538
  truthDocPath,
6872
- path: normalizeReferencePath(truthDocPath, entry),
6873
- source: "frontmatter"
7539
+ path: entry,
7540
+ source: "source-references"
6874
7541
  });
6875
7542
  }
6876
7543
  for (const match of parsed.content.matchAll(yamlFencePattern)) {
@@ -7245,9 +7912,9 @@ var runCheck = async (cwd, options = {}) => {
7245
7912
 
7246
7913
  // src/context-pack/build.ts
7247
7914
  import fs22 from "fs/promises";
7248
- import path12 from "path";
7915
+ import path13 from "path";
7249
7916
  import fg8 from "fast-glob";
7250
- var uniqueSorted2 = (values) => [...new Set(values)].sort();
7917
+ var uniqueSorted3 = (values) => [...new Set(values)].sort();
7251
7918
  var repoRootPrefixes2 = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
7252
7919
  var isGlobReference2 = (referencePath) => /[*?[\]{}()]/u.test(referencePath);
7253
7920
  var normalizeDocReferencePath = (docPath, referencePath) => {
@@ -7256,12 +7923,12 @@ var normalizeDocReferencePath = (docPath, referencePath) => {
7256
7923
  return null;
7257
7924
  }
7258
7925
  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));
7926
+ const normalized = isRepoRelative ? path13.posix.normalize(strippedPath) : path13.posix.normalize(path13.posix.join(path13.posix.dirname(docPath), strippedPath));
7260
7927
  return normalized === ".." || normalized.startsWith("../") ? null : normalized;
7261
7928
  };
7262
7929
  var readIfExists = async (rootDir, filePath) => {
7263
7930
  try {
7264
- return await fs22.readFile(path12.join(rootDir, filePath), "utf8");
7931
+ return await fs22.readFile(path13.join(rootDir, filePath), "utf8");
7265
7932
  } catch {
7266
7933
  return null;
7267
7934
  }
@@ -7295,7 +7962,7 @@ var boundedContent = (filePath, content, warnings) => {
7295
7962
  };
7296
7963
  var documentsFor = async (rootDir, paths, warnings) => {
7297
7964
  const documents = [];
7298
- for (const filePath of uniqueSorted2(paths)) {
7965
+ for (const filePath of uniqueSorted3(paths)) {
7299
7966
  const content = await readIfExists(rootDir, filePath);
7300
7967
  if (content !== null) {
7301
7968
  const bounded = boundContent(content);
@@ -7314,7 +7981,7 @@ var documentsFor = async (rootDir, paths, warnings) => {
7314
7981
  };
7315
7982
  var sourceFilesFor = async (rootDir, paths, warnings) => {
7316
7983
  const sourceFiles = [];
7317
- for (const filePath of uniqueSorted2(paths)) {
7984
+ for (const filePath of uniqueSorted3(paths)) {
7318
7985
  const content = await readIfExists(rootDir, filePath);
7319
7986
  if (content !== null) {
7320
7987
  sourceFiles.push(boundedContent(filePath, content, warnings));
@@ -7348,16 +8015,16 @@ var sourceOfTruthPathsFor = async (rootDir, docs, truthDocPaths) => {
7348
8015
  }
7349
8016
  }
7350
8017
  }
7351
- return uniqueSorted2(sourcePaths);
8018
+ return uniqueSorted3(sourcePaths);
7352
8019
  };
7353
8020
  var writePathsFor = (workflow, routeIndexPath, truthDocs, routes) => {
7354
8021
  if (workflow === "truth-sync") {
7355
- return uniqueSorted2([routeIndexPath, ...truthDocs]);
8022
+ return uniqueSorted3([routeIndexPath, ...truthDocs]);
7356
8023
  }
7357
8024
  if (workflow === "truth-document") {
7358
- return uniqueSorted2([routeIndexPath, ...truthDocs]);
8025
+ return uniqueSorted3([routeIndexPath, ...truthDocs]);
7359
8026
  }
7360
- return uniqueSorted2(routes.flatMap((route) => route.codeSurface));
8027
+ return uniqueSorted3(routes.flatMap((route) => route.codeSurface));
7361
8028
  };
7362
8029
  var testCommandsFor = (affectedTests) => {
7363
8030
  return affectedTests.length === 0 ? ["npm test"] : [`npm test -- ${affectedTests.join(" ")}`];
@@ -7380,7 +8047,7 @@ var buildContextPack = async (cwd, options) => {
7380
8047
  });
7381
8048
  }
7382
8049
  const sourceOfTruthPaths = await sourceOfTruthPathsFor(rootDir, repoIndex.docs, truthDocPaths);
7383
- const sourceFilePaths = uniqueSorted2([
8050
+ const sourceFilePaths = uniqueSorted3([
7384
8051
  ...impactSet?.changedFiles.filter((file) => !file.deleted).map((file) => file.path) ?? [],
7385
8052
  ...sourceOfTruthPaths
7386
8053
  ]);
@@ -7431,7 +8098,7 @@ var renderContextPackMarkdown = (pack) => {
7431
8098
  import { execa as execa5 } from "execa";
7432
8099
 
7433
8100
  // src/workflow-state/action-context.ts
7434
- var uniqueSorted3 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
8101
+ var uniqueSorted4 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
7435
8102
  var helperCommandsFor = (manifestEntry) => (manifestEntry.helpers ?? []).map((helper) => ({
7436
8103
  id: helper.id,
7437
8104
  runner: helper.runner,
@@ -7441,8 +8108,8 @@ var helperCommandsFor = (manifestEntry) => (manifestEntry.helpers ?? []).map((he
7441
8108
  var evidenceFor = (manifestEntry) => manifestEntry.requiredGates.filter((gate) => /evidence|ownership|containment/iu.test(gate));
7442
8109
  var baseContext = (manifestEntry, mode, allowedWritePaths, forbiddenWritePaths, writeLeaseRequired) => ({
7443
8110
  mode,
7444
- allowedWritePaths: uniqueSorted3(allowedWritePaths),
7445
- forbiddenWritePaths: uniqueSorted3(forbiddenWritePaths),
8111
+ allowedWritePaths: uniqueSorted4(allowedWritePaths),
8112
+ forbiddenWritePaths: uniqueSorted4(forbiddenWritePaths),
7446
8113
  stopConditions: [...manifestEntry.negativeTriggers, ...manifestEntry.forbiddenAdjacency],
7447
8114
  requiredEvidence: evidenceFor(manifestEntry),
7448
8115
  helperValidationCommands: helperCommandsFor(manifestEntry),
@@ -7504,7 +8171,7 @@ var helperCommandsFor2 = (workflow) => (TRUTHMARK_WORKFLOW_MANIFEST[workflow].he
7504
8171
  argv: [...helper.command.argv],
7505
8172
  optional: helper.optional
7506
8173
  }));
7507
- var uniqueSorted4 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
8174
+ var uniqueSorted5 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
7508
8175
  var isWriteCapable = (workflow) => !["truthmark-preview", "truthmark-check"].includes(workflow);
7509
8176
  var DEFAULT_BASE_CANDIDATES = ["@{upstream}", "origin/main", "main", "origin/master", "master"];
7510
8177
  var selectComparisonBase = async (rootDir, suppliedBase) => {
@@ -7522,7 +8189,7 @@ var selectComparisonBase = async (rootDir, suppliedBase) => {
7522
8189
  }
7523
8190
  return null;
7524
8191
  };
7525
- var routeFilesFor = (repoIndex) => uniqueSorted4(repoIndex.routeMap.routes.map((route) => route.sourcePath));
8192
+ var routeFilesFor = (repoIndex) => uniqueSorted5(repoIndex.routeMap.routes.map((route) => route.sourcePath));
7526
8193
  var hasUnmappedFunctionalChange = (impactSet) => impactSet?.diagnostics.some(
7527
8194
  (diagnostic) => diagnostic.category === "impact" && /not mapped to a Truthmark route|no affected truth document/u.test(diagnostic.message)
7528
8195
  ) ?? false;
@@ -7555,7 +8222,7 @@ var contextDataFor = (workflow, repoIndex, config, impactSet) => {
7555
8222
  return {};
7556
8223
  }
7557
8224
  const routeFiles = routeFilesFor(repoIndex);
7558
- const truthDocs = uniqueSorted4(
8225
+ const truthDocs = uniqueSorted5(
7559
8226
  impactSet?.affectedTruthDocs ?? repoIndex.routeMap.routes.flatMap((route) => route.truthDocs)
7560
8227
  );
7561
8228
  return {
@@ -7564,7 +8231,7 @@ var contextDataFor = (workflow, repoIndex, config, impactSet) => {
7564
8231
  truthRoot: config.truthmark.paths.truthRoot,
7565
8232
  truthDocs,
7566
8233
  starterTruthDocs: workflow === "truthmark-structure" ? truthDocs : [],
7567
- codeWritePaths: workflow === "truthmark-realize" ? uniqueSorted4(impactSet?.affectedRoutes.flatMap((route) => route.codeSurface) ?? []) : [],
8234
+ codeWritePaths: workflow === "truthmark-realize" ? uniqueSorted5(impactSet?.affectedRoutes.flatMap((route) => route.codeSurface) ?? []) : [],
7568
8235
  portalEnabled: config.truthmark.generated.portal.enabled,
7569
8236
  portalOutputPath: config.truthmark.paths.portalOutput,
7570
8237
  routes: repoIndex.routeMap.routes