truthmark 1.6.0 → 2.1.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
@@ -266,11 +266,11 @@ var DEFAULT_PLATFORMS = [
266
266
  var truthmarkConfigSchema = {
267
267
  type: "object",
268
268
  additionalProperties: false,
269
- required: ["version", "authority"],
269
+ required: ["version", "truthmark"],
270
270
  properties: {
271
271
  version: {
272
272
  type: "integer",
273
- const: 1
273
+ const: 2
274
274
  },
275
275
  platforms: {
276
276
  type: "array",
@@ -281,52 +281,58 @@ var truthmarkConfigSchema = {
281
281
  },
282
282
  minItems: 1
283
283
  },
284
- docs: {
284
+ truthmark: {
285
285
  type: "object",
286
- nullable: true,
287
286
  additionalProperties: false,
288
- required: ["layout", "roots", "routing"],
287
+ required: ["workspace", "routes", "truth", "templates", "generated"],
289
288
  properties: {
290
- layout: {
291
- type: "string",
292
- const: "hierarchical"
289
+ workspace: {
290
+ type: "string"
293
291
  },
294
- roots: {
292
+ routes: {
295
293
  type: "object",
296
- required: [],
297
- additionalProperties: {
298
- type: "string"
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" }
299
317
  }
300
318
  },
301
- routing: {
319
+ generated: {
302
320
  type: "object",
303
321
  additionalProperties: false,
304
- required: ["root_index", "area_files_root", "default_area", "max_delegation_depth"],
322
+ required: ["portal"],
305
323
  properties: {
306
- root_index: {
307
- type: "string"
308
- },
309
- area_files_root: {
310
- type: "string"
311
- },
312
- default_area: {
313
- type: "string"
314
- },
315
- max_delegation_depth: {
316
- type: "integer",
317
- const: 1
324
+ portal: {
325
+ type: "object",
326
+ additionalProperties: false,
327
+ required: ["enabled"],
328
+ properties: {
329
+ enabled: { type: "boolean" }
330
+ }
318
331
  }
319
332
  }
320
333
  }
321
334
  }
322
335
  },
323
- authority: {
324
- type: "array",
325
- items: {
326
- type: "string"
327
- },
328
- minItems: 1
329
- },
330
336
  instruction_targets: {
331
337
  type: "array",
332
338
  nullable: true,
@@ -334,22 +340,6 @@ var truthmarkConfigSchema = {
334
340
  type: "string"
335
341
  }
336
342
  },
337
- "truthmark-portal": {
338
- type: "object",
339
- additionalProperties: false,
340
- required: [],
341
- properties: {
342
- enabled: {
343
- type: "boolean"
344
- },
345
- output: {
346
- type: "string"
347
- },
348
- template: {
349
- type: "string"
350
- }
351
- }
352
- },
353
343
  frontmatter: {
354
344
  type: "object",
355
345
  nullable: true,
@@ -383,44 +373,39 @@ var truthmarkConfigSchema = {
383
373
  };
384
374
 
385
375
  // src/config/defaults.ts
386
- var DEFAULT_DOCS_HIERARCHY = {
387
- layout: "hierarchical",
388
- roots: {
389
- ai: "docs/ai",
390
- standards: "docs/standards",
391
- architecture: "docs/architecture",
392
- truth: "docs/truth"
393
- },
394
- routing: {
395
- root_index: "docs/truthmark/areas.md",
396
- area_files_root: "docs/truthmark/areas",
376
+ var DEFAULT_TRUTHMARK_WORKSPACE = {
377
+ workspace: "docs/truthmark",
378
+ routes: {
379
+ index: "routes/areas.md",
380
+ areas: "routes/areas",
397
381
  default_area: "repository",
398
382
  max_delegation_depth: 1
383
+ },
384
+ truth: {
385
+ root: "truth"
386
+ },
387
+ templates: {
388
+ root: "templates"
389
+ },
390
+ generated: {
391
+ portal: {
392
+ enabled: false
393
+ }
399
394
  }
400
395
  };
401
- var DEFAULT_AUTHORITY = [
402
- DEFAULT_DOCS_HIERARCHY.routing.root_index,
403
- `${DEFAULT_DOCS_HIERARCHY.routing.area_files_root}/**/*.md`,
404
- `${DEFAULT_DOCS_HIERARCHY.roots.ai}/**/*.md`,
405
- `${DEFAULT_DOCS_HIERARCHY.roots.standards}/**/*.md`,
406
- `${DEFAULT_DOCS_HIERARCHY.roots.architecture}/**/*.md`,
407
- `${DEFAULT_DOCS_HIERARCHY.roots.truth}/**/*.md`
408
- ];
409
396
  var DEFAULT_INSTRUCTION_TARGETS = ["AGENTS.md"];
410
- var DEFAULT_TRUTHMARK_PORTAL = {
411
- enabled: false,
412
- output: "docs/truthmark-portal",
413
- template: "default"
414
- };
415
397
  var createDefaultRawConfig = () => ({
416
- version: 1,
398
+ version: 2,
417
399
  platforms: [...DEFAULT_PLATFORMS],
418
- docs: {
419
- layout: DEFAULT_DOCS_HIERARCHY.layout,
420
- roots: { ...DEFAULT_DOCS_HIERARCHY.roots },
421
- routing: { ...DEFAULT_DOCS_HIERARCHY.routing }
400
+ truthmark: {
401
+ workspace: DEFAULT_TRUTHMARK_WORKSPACE.workspace,
402
+ routes: { ...DEFAULT_TRUTHMARK_WORKSPACE.routes },
403
+ truth: { ...DEFAULT_TRUTHMARK_WORKSPACE.truth },
404
+ templates: { ...DEFAULT_TRUTHMARK_WORKSPACE.templates },
405
+ generated: {
406
+ portal: { ...DEFAULT_TRUTHMARK_WORKSPACE.generated.portal }
407
+ }
422
408
  },
423
- authority: [...DEFAULT_AUTHORITY],
424
409
  instruction_targets: [...DEFAULT_INSTRUCTION_TARGETS],
425
410
  frontmatter: {
426
411
  required: [],
@@ -429,21 +414,37 @@ var createDefaultRawConfig = () => ({
429
414
  ignore: ["node_modules/**", "vendor/**", "dist/**", "build/**"]
430
415
  });
431
416
  var createDefaultConfig = () => ({
432
- version: 1,
417
+ version: 2,
433
418
  platforms: [...DEFAULT_PLATFORMS],
434
- docs: {
435
- layout: DEFAULT_DOCS_HIERARCHY.layout,
436
- roots: { ...DEFAULT_DOCS_HIERARCHY.roots },
437
- routing: {
438
- rootIndex: DEFAULT_DOCS_HIERARCHY.routing.root_index,
439
- areaFilesRoot: DEFAULT_DOCS_HIERARCHY.routing.area_files_root,
440
- defaultArea: DEFAULT_DOCS_HIERARCHY.routing.default_area,
441
- maxDelegationDepth: DEFAULT_DOCS_HIERARCHY.routing.max_delegation_depth
442
- }
419
+ truthmark: {
420
+ workspace: DEFAULT_TRUTHMARK_WORKSPACE.workspace,
421
+ 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
426
+ },
427
+ truth: { root: DEFAULT_TRUTHMARK_WORKSPACE.truth.root },
428
+ templates: { root: DEFAULT_TRUTHMARK_WORKSPACE.templates.root },
429
+ generated: {
430
+ portal: { ...DEFAULT_TRUTHMARK_WORKSPACE.generated.portal }
431
+ },
432
+ paths: {
433
+ routesIndex: "docs/truthmark/routes/areas.md",
434
+ routeAreasRoot: "docs/truthmark/routes/areas",
435
+ truthRoot: "docs/truthmark/truth",
436
+ templatesRoot: "docs/truthmark/templates",
437
+ portalOutput: "docs/truthmark/generated/portal",
438
+ portalTemplate: "docs/truthmark/templates/portal.html"
439
+ },
440
+ controlledPaths: [
441
+ "docs/truthmark/routes/areas.md",
442
+ "docs/truthmark/routes/areas/**/*.md",
443
+ "docs/truthmark/truth/**/*.md",
444
+ "docs/truthmark/templates/*.md"
445
+ ]
443
446
  },
444
- authority: [...DEFAULT_AUTHORITY],
445
447
  instructionTargets: [...DEFAULT_INSTRUCTION_TARGETS],
446
- truthmarkPortal: { ...DEFAULT_TRUTHMARK_PORTAL },
447
448
  frontmatter: {
448
449
  required: [],
449
450
  recommended: ["status", "doc_type", "last_reviewed", "source_of_truth"]
@@ -461,6 +462,7 @@ var TRUTH_DOCUMENT_KINDS = [
461
462
  "operations",
462
463
  "test-behavior"
463
464
  ];
465
+ var DEFAULT_WORKSPACE_TRUTH_DOCS_ROOT = "docs/truthmark/truth";
464
466
  var slugify = (value) => {
465
467
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
466
468
  };
@@ -480,25 +482,10 @@ var isTruthDocumentKind = (value) => {
480
482
  };
481
483
  var inferTruthDocumentKindFromPath = (documentPath, options = {}) => {
482
484
  const normalizedPath = documentPath.replaceAll("\\", "/");
483
- const truthDocsRoot = options.truthDocsRoot?.replaceAll("\\", "/").replace(/\/+$/u, "");
484
- if (truthDocsRoot && normalizedPath.startsWith(`${truthDocsRoot}/`) || normalizedPath.startsWith("docs/truth/")) {
485
+ const truthDocsRoot = (options.truthDocsRoot ?? DEFAULT_WORKSPACE_TRUTH_DOCS_ROOT)?.replaceAll("\\", "/").replace(/\/+$/u, "");
486
+ if (truthDocsRoot && normalizedPath.startsWith(`${truthDocsRoot}/`)) {
485
487
  return "behavior";
486
488
  }
487
- if (normalizedPath.startsWith("docs/contracts/") || normalizedPath.startsWith("docs/contract/") || normalizedPath.startsWith("docs/api/")) {
488
- return "contract";
489
- }
490
- if (normalizedPath.startsWith("docs/architecture/")) {
491
- return "architecture";
492
- }
493
- if (normalizedPath.startsWith("docs/workflows/") || normalizedPath.startsWith("docs/workflow/")) {
494
- return "workflow";
495
- }
496
- if (normalizedPath.startsWith("docs/operations/") || normalizedPath.startsWith("docs/platform/")) {
497
- return "operations";
498
- }
499
- if (normalizedPath.startsWith("docs/testing/") || normalizedPath.startsWith("docs/tests/")) {
500
- return "test-behavior";
501
- }
502
489
  return null;
503
490
  };
504
491
  var findTruthDocumentsYamlFenceRange = (sectionLines) => {
@@ -738,9 +725,8 @@ var parseAreasMarkdown = (source, options = {}) => {
738
725
  };
739
726
 
740
727
  // src/truth/docs.ts
741
- var DEFAULT_TRUTH_DOCS_ROOT = DEFAULT_DOCS_HIERARCHY.roots.truth;
742
728
  var resolveTruthDocsRoot = (config) => {
743
- return config.docs.roots.truth ?? DEFAULT_TRUTH_DOCS_ROOT;
729
+ return config.truthmark.paths.truthRoot;
744
730
  };
745
731
 
746
732
  // src/templates/init-files.ts
@@ -759,11 +745,11 @@ var titleCase = (value) => {
759
745
  return value.split(/[-_\s]+/u).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
760
746
  };
761
747
  var renderHierarchicalAreasIndexTemplate = (config) => {
762
- const defaultArea = config.docs.routing.defaultArea;
763
- const childPath = `${config.docs.routing.areaFilesRoot}/${defaultArea}.md`;
748
+ const defaultArea = config.truthmark.routes.defaultArea;
749
+ const childPath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
764
750
  const title = titleCase(defaultArea);
765
751
  const sourceOfTruth = resolveRelativePath(
766
- config.docs.routing.rootIndex,
752
+ config.truthmark.paths.routesIndex,
767
753
  ".truthmark/config.yml"
768
754
  );
769
755
  return [
@@ -792,11 +778,11 @@ var renderHierarchicalAreasIndexTemplate = (config) => {
792
778
  ].join("\n");
793
779
  };
794
780
  var renderChildAreaTemplate = (config) => {
795
- const defaultArea = config.docs.routing.defaultArea;
781
+ const defaultArea = config.truthmark.routes.defaultArea;
796
782
  const title = titleCase(defaultArea);
797
783
  const truthDocsRoot = truthRoot(config);
798
784
  const leafTruthDoc = `${truthDocsRoot}/${defaultArea}/overview.md`;
799
- const templatePath = `${config.docs.routing.areaFilesRoot}/${defaultArea}.md`;
785
+ const templatePath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
800
786
  const sourceOfTruth = resolveRelativePath(templatePath, ".truthmark/config.yml");
801
787
  return [
802
788
  "---",
@@ -830,7 +816,7 @@ var renderTruthRootReadmeTemplate = (config = createDefaultConfig()) => {
830
816
  const templatePath = `${truthRoot(config)}/README.md`;
831
817
  const sourceOfTruth = resolveRelativePath(
832
818
  templatePath,
833
- config.docs.routing.rootIndex
819
+ config.truthmark.paths.routesIndex
834
820
  );
835
821
  return [
836
822
  "---",
@@ -850,12 +836,12 @@ var renderTruthRootReadmeTemplate = (config = createDefaultConfig()) => {
850
836
  ].join("\n");
851
837
  };
852
838
  var renderTruthDomainReadmeTemplate = (config) => {
853
- const defaultArea = config.docs.routing.defaultArea;
839
+ const defaultArea = config.truthmark.routes.defaultArea;
854
840
  const title = titleCase(defaultArea);
855
841
  const templatePath = `${truthRoot(config)}/${defaultArea}/README.md`;
856
842
  const sourceOfTruth = resolveRelativePath(
857
843
  templatePath,
858
- `${config.docs.routing.areaFilesRoot}/${defaultArea}.md`
844
+ `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`
859
845
  );
860
846
  return [
861
847
  "---",
@@ -878,12 +864,103 @@ var renderTruthDomainReadmeTemplate = (config) => {
878
864
  ""
879
865
  ].join("\n");
880
866
  };
881
- var BEHAVIOR_DOC_TEMPLATE_PATH = "docs/templates/behavior-doc.md";
882
- var CONTRACT_DOC_TEMPLATE_PATH = "docs/templates/contract-doc.md";
883
- var ARCHITECTURE_DOC_TEMPLATE_PATH = "docs/templates/architecture-doc.md";
884
- var WORKFLOW_DOC_TEMPLATE_PATH = "docs/templates/workflow-doc.md";
885
- var OPERATIONS_DOC_TEMPLATE_PATH = "docs/templates/operations-doc.md";
886
- var TEST_BEHAVIOR_DOC_TEMPLATE_PATH = "docs/templates/test-behavior-doc.md";
867
+ var BEHAVIOR_DOC_TEMPLATE_PATH = "docs/truthmark/templates/behavior-doc.md";
868
+ var renderTemplateSection = (section) => {
869
+ return [
870
+ section.heading,
871
+ "",
872
+ "<!--",
873
+ ...section.guidance,
874
+ "-->",
875
+ "",
876
+ `{{${section.placeholder}}}`,
877
+ ""
878
+ ];
879
+ };
880
+ var titleToPlaceholder = (title) => {
881
+ return title.replace(/^#+\s+/u, "").toLowerCase().replaceAll(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
882
+ };
883
+ var findTemplateSectionHeadings = (template) => {
884
+ const matches = [];
885
+ let fencedCodeMarker = null;
886
+ let fencedCodeLength = 0;
887
+ for (const lineMatch of template.matchAll(/^.*(?:\r?\n|$)/gm)) {
888
+ const rawLine = lineMatch[0];
889
+ if (rawLine.length === 0) {
890
+ continue;
891
+ }
892
+ const line = rawLine.replace(/\r?\n$/u, "");
893
+ const fenceMatch = /^(?: {0,3})(`{3,}|~{3,})/u.exec(line);
894
+ if (fenceMatch) {
895
+ const marker = fenceMatch[1]?.[0];
896
+ const length = fenceMatch[1]?.length ?? 0;
897
+ if (fencedCodeMarker === null) {
898
+ fencedCodeMarker = marker;
899
+ fencedCodeLength = length;
900
+ } else if (marker === fencedCodeMarker && length >= fencedCodeLength) {
901
+ fencedCodeMarker = null;
902
+ fencedCodeLength = 0;
903
+ }
904
+ continue;
905
+ }
906
+ if (fencedCodeMarker === null && /^## .+$/u.test(line)) {
907
+ matches.push({ heading: line.trim(), index: lineMatch.index });
908
+ }
909
+ }
910
+ return matches;
911
+ };
912
+ var parseTemplateSections = (template) => {
913
+ const matches = findTemplateSectionHeadings(template);
914
+ if (matches.length === 0) {
915
+ return { preamble: template.trimEnd(), sections: [] };
916
+ }
917
+ const sections = matches.map((match, index) => {
918
+ const start = match.index;
919
+ const next = matches[index + 1];
920
+ const end = next?.index ?? template.length;
921
+ return {
922
+ heading: match.heading,
923
+ block: template.slice(start, end).trimEnd()
924
+ };
925
+ });
926
+ return {
927
+ preamble: template.slice(0, matches[0]?.index ?? 0).trimEnd(),
928
+ sections
929
+ };
930
+ };
931
+ var mergeTruthDocTemplate = (existingTemplate, defaultTemplate) => {
932
+ if (existingTemplate.trim().length === 0) {
933
+ return defaultTemplate;
934
+ }
935
+ const defaultParsed = parseTemplateSections(defaultTemplate);
936
+ const existingParsed = parseTemplateSections(existingTemplate);
937
+ const defaultHeadings = new Set(defaultParsed.sections.map((section) => section.heading));
938
+ const customBeforeDefault = /* @__PURE__ */ new Map();
939
+ const trailingCustomSections = [];
940
+ existingParsed.sections.forEach((section, index) => {
941
+ if (defaultHeadings.has(section.heading)) {
942
+ return;
943
+ }
944
+ const nextDefaultSection = existingParsed.sections.slice(index + 1).find((candidate) => defaultHeadings.has(candidate.heading));
945
+ if (nextDefaultSection) {
946
+ const bucket = customBeforeDefault.get(nextDefaultSection.heading) ?? [];
947
+ bucket.push(section);
948
+ customBeforeDefault.set(nextDefaultSection.heading, bucket);
949
+ return;
950
+ }
951
+ trailingCustomSections.push(section);
952
+ });
953
+ const mergedSections = defaultParsed.sections.flatMap((section) => [
954
+ ...customBeforeDefault.get(section.heading) ?? [],
955
+ section
956
+ ]);
957
+ return [
958
+ existingParsed.preamble,
959
+ ...mergedSections.map((section) => section.block),
960
+ ...trailingCustomSections.map((section) => section.block),
961
+ ""
962
+ ].filter((block) => block.length > 0).join("\n\n");
963
+ };
887
964
  var renderBehaviorDocTemplateFile = () => {
888
965
  return [
889
966
  "---",
@@ -899,81 +976,130 @@ var renderBehaviorDocTemplateFile = () => {
899
976
  "",
900
977
  "## Purpose",
901
978
  "",
902
- "<!-- State why this feature exists, the user or system outcome it protects, and the problem it solves. Keep roadmap or implementation plans out of this section. -->",
979
+ "<!--",
980
+ "State the user/system outcome this behavior protects and why it exists.",
981
+ "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.",
983
+ "-->",
903
984
  "",
904
985
  "{{purpose}}",
905
986
  "",
906
987
  "## Scope",
907
988
  "",
908
- "{{scope}}",
909
- "",
910
989
  "<!--",
911
- "This doc must own one coherent behavior surface.",
912
- "Split into another leaf doc when content introduces:",
913
- "- a distinct user or system outcome",
914
- "- a separate lifecycle or state machine",
915
- "- an unrelated rule family",
916
- "- a different external contract",
917
- "- code that should route through a different owner",
990
+ "Define the one coherent behavior surface this document owns.",
991
+ "Include in-scope actors, entrypoints, state/data owned by this doc, and explicit handoffs to neighboring truth docs.",
992
+ "Split into another leaf doc when content introduces a distinct outcome, state machine, rule family, external contract, or route owner.",
918
993
  "Keep README.md files as indexes only.",
919
994
  "-->",
920
995
  "",
996
+ "{{scope}}",
997
+ "",
921
998
  "This doc was created from the editable behavior-doc template at {{template_path}}.",
922
999
  "",
923
1000
  "## Current Behavior",
924
1001
  "",
925
- "<!-- Describe implemented behavior in present tense. Do not include desired future behavior. -->",
1002
+ "<!--",
1003
+ "Describe only current implemented behavior in present tense.",
1004
+ "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.",
1006
+ "-->",
926
1007
  "",
927
1008
  "{{current_behavior}}",
928
1009
  "",
929
1010
  "## Core Rules",
930
1011
  "",
931
- "<!-- Capture stable business rules, invariants, precedence rules, validation rules, and must-never constraints. Omit incidental implementation details. -->",
1012
+ "<!--",
1013
+ "Capture stable business rules, invariants, precedence rules, validation rules, and must-never constraints.",
1014
+ "Separate rules from incidental implementation details; cite current implementation or tests for rule enforcement.",
1015
+ "-->",
932
1016
  "",
933
1017
  "{{core_rules}}",
934
1018
  "",
935
1019
  "## Flows And States",
936
1020
  "",
937
- "<!-- Use for route switches, state transitions, lifecycle stages, retries, fallbacks, and important error paths. Write 'None beyond current behavior.' when no distinct flow or state model exists. -->",
1021
+ "<!--",
1022
+ "Document state transitions, lifecycle stages, retries, fallbacks, route switches, and important error paths.",
1023
+ "State 'None beyond current behavior.' when this behavior has no distinct flow or state model.",
1024
+ "-->",
938
1025
  "",
939
1026
  "{{flows_and_states}}",
940
1027
  "",
941
1028
  "## Contracts",
942
1029
  "",
943
- "<!-- Capture user-visible or integration contracts: CLI/API shape, inputs, outputs, diagnostics, files, events, permissions, or links to canonical contract docs. Avoid duplicating a separate canonical contract doc. -->",
1030
+ "<!--",
1031
+ "Capture user-visible or integration contracts: CLI/API shape, inputs, outputs, diagnostics, files, events, permissions, or links to canonical contract docs.",
1032
+ "Avoid duplicating a separate canonical contract doc; link to it when contract ownership lives elsewhere.",
1033
+ "-->",
944
1034
  "",
945
1035
  "{{contracts}}",
946
1036
  "",
947
1037
  "## Product Decisions",
948
1038
  "",
949
- "<!-- Keep active decisions only. Replace stale decisions instead of appending historical logs. -->",
1039
+ "<!--",
1040
+ "Keep active decisions only, dated inline when added or changed.",
1041
+ "Explain decisions that shape behavior, boundaries, rejected alternatives, or migration constraints; replace stale decisions instead of appending historical logs.",
1042
+ "-->",
950
1043
  "",
951
1044
  "{{decision}}",
952
1045
  "",
953
1046
  "## Rationale",
954
1047
  "",
955
- "<!-- Explain why the current behavior and active decisions are this way, including tradeoffs. -->",
1048
+ "<!--",
1049
+ "Explain why the current behavior and active decisions are this way, including tradeoffs and constraints.",
1050
+ "Tie rationale to evidence-backed behavior; do not use this as a changelog.",
1051
+ "-->",
956
1052
  "",
957
1053
  "{{rationale}}",
958
1054
  "",
959
1055
  "## Non-Goals",
960
1056
  "",
961
- "<!-- Name adjacent behavior this doc intentionally does not own, especially tempting future expansions. -->",
1057
+ "<!--",
1058
+ "Name adjacent behavior this doc intentionally does not own, especially tempting future expansions or neighboring route owners.",
1059
+ "Use this section to prevent scope creep and duplicate truth ownership.",
1060
+ "-->",
962
1061
  "",
963
1062
  "{{non_goals}}",
964
1063
  "",
965
1064
  "## Maintenance Notes",
966
1065
  "",
967
- "<!-- List related tests, routing cautions, migration notes, and common drift risks for future agents. Keep this operational, not historical. -->",
1066
+ "<!--",
1067
+ "List related tests, routing cautions, migration notes, evidence drift risks, and review triggers for future maintainers or agents.",
1068
+ "Keep this operational and current-state focused, not historical.",
1069
+ "-->",
968
1070
  "",
969
1071
  "{{maintenance_notes}}",
970
1072
  ""
971
1073
  ].join("\n");
972
1074
  };
1075
+ var sectionSpec = (heading, guidance, placeholder = titleToPlaceholder(heading)) => ({ heading, guidance, placeholder });
1076
+ var PURPOSE_SECTION = sectionSpec("## Purpose", [
1077
+ "State the software-engineering outcome this document protects and why the documented surface exists.",
1078
+ "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."
1080
+ ]);
1081
+ var SCOPE_SECTION = sectionSpec("## Scope", [
1082
+ "Define the one coherent surface this document owns, including actors, entrypoints, owned state/data, and handoffs to neighboring truth docs.",
1083
+ "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
+ ]);
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");
1090
+ var RATIONALE_SECTION = sectionSpec("## Rationale", [
1091
+ "Explain why the current behavior, structure, or contract is this way, including tradeoffs and constraints.",
1092
+ "Tie rationale to evidence-backed facts and active decisions; do not use this as a changelog."
1093
+ ]);
1094
+ var NON_GOALS_SECTION = sectionSpec("## Non-Goals", [
1095
+ "Name adjacent behavior, responsibilities, interfaces, or future expansions this doc intentionally does not own.",
1096
+ "Use this section to prevent scope creep and duplicate truth ownership."
1097
+ ]);
1098
+ var MAINTENANCE_NOTES_SECTION = sectionSpec("## Maintenance Notes", [
1099
+ "List related tests, routing cautions, migration notes, compatibility risks, evidence drift risks, and review triggers for future maintainers or agents.",
1100
+ "Keep this operational and current-state focused, not historical."
1101
+ ]);
973
1102
  var renderTypedTruthDocTemplate = (truthKind, docType, title, sections) => {
974
- const placeholderNameForSection = (section) => {
975
- return section.replace(/^#+\s+/u, "").toLowerCase().replaceAll(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
976
- };
977
1103
  return [
978
1104
  "---",
979
1105
  "status: active",
@@ -986,86 +1112,153 @@ var renderTypedTruthDocTemplate = (truthKind, docType, title, sections) => {
986
1112
  "",
987
1113
  `# ${title}`,
988
1114
  "",
989
- "## Purpose",
990
- "",
991
- "{{purpose}}",
992
- "",
993
- "## Scope",
994
- "",
995
- "{{scope}}",
996
- "",
997
- ...sections.flatMap((section) => [
998
- section,
999
- "",
1000
- `{{${placeholderNameForSection(section)}}}`,
1001
- ""
1002
- ]),
1003
- "## Product Decisions",
1004
- "",
1005
- "{{decision}}",
1006
- "",
1007
- "## Rationale",
1008
- "",
1009
- "{{rationale}}",
1010
- "",
1011
- "## Non-Goals",
1012
- "",
1013
- "{{non_goals}}",
1014
- "",
1015
- "## Maintenance Notes",
1016
- "",
1017
- "{{maintenance_notes}}",
1018
- ""
1115
+ ...renderTemplateSection(PURPOSE_SECTION),
1116
+ ...renderTemplateSection(SCOPE_SECTION),
1117
+ ...sections.flatMap(renderTemplateSection),
1118
+ ...renderTemplateSection(PRODUCT_DECISIONS_SECTION),
1119
+ ...renderTemplateSection(RATIONALE_SECTION),
1120
+ ...renderTemplateSection(NON_GOALS_SECTION),
1121
+ ...renderTemplateSection(MAINTENANCE_NOTES_SECTION)
1019
1122
  ].join("\n");
1020
1123
  };
1021
1124
  var renderContractDocTemplateFile = () => {
1022
1125
  return renderTypedTruthDocTemplate("contract", "contract", "{{title}}", [
1023
- "## Contract Surface",
1024
- "## Inputs",
1025
- "## Outputs",
1026
- "## Errors And Diagnostics",
1027
- "## Compatibility Rules",
1028
- "## Versioning And Migration"
1126
+ sectionSpec("## Contract Surface", [
1127
+ "Identify the owned API, CLI, file format, event, protocol, permission boundary, or integration surface.",
1128
+ "State consumers/producers, stability level, and the source files/tests that define the contract."
1129
+ ]),
1130
+ sectionSpec("## Inputs", [
1131
+ "Document accepted parameters, payloads, files, environment/config keys, permissions, and validation rules.",
1132
+ "Include required/optional status, defaults, constraints, and normalization behavior."
1133
+ ]),
1134
+ sectionSpec("## Outputs", [
1135
+ "Document returned values, emitted files/events, state changes, side effects, and success diagnostics.",
1136
+ "Make externally observable behavior explicit enough for compatibility review."
1137
+ ]),
1138
+ sectionSpec("## Errors And Diagnostics", [
1139
+ "List error classes, exit/status codes, user-facing diagnostics, retries, and recoverability expectations.",
1140
+ "Distinguish validation errors, dependency failures, authorization failures, and internal faults when applicable."
1141
+ ]),
1142
+ sectionSpec("## Compatibility Rules", [
1143
+ "State backward/forward compatibility guarantees, tolerated inputs, deprecation rules, and breaking-change triggers.",
1144
+ "Include compatibility tests or review gates that protect the contract."
1145
+ ]),
1146
+ sectionSpec("## Versioning And Migration", [
1147
+ "Document version negotiation, schema/API version fields, rollout requirements, migration steps, and rollback expectations.",
1148
+ "State 'Not versioned' only when the implementation truly has no versioning or migration surface."
1149
+ ])
1029
1150
  ]);
1030
1151
  };
1031
1152
  var renderArchitectureDocTemplateFile = () => {
1032
1153
  return renderTypedTruthDocTemplate("architecture", "architecture", "{{title}}", [
1033
- "## System Role",
1034
- "## Boundaries",
1035
- "## Components",
1036
- "## Data And Control Flow",
1037
- "## Ownership",
1038
- "## Cross-Cutting Constraints"
1154
+ sectionSpec("## System Role", [
1155
+ "Describe the current architectural role of this subsystem/component in the larger system.",
1156
+ "State the primary responsibilities, consumers, providers, and why this boundary exists now."
1157
+ ]),
1158
+ sectionSpec("## Boundaries", [
1159
+ "Define owned code/config/data, external dependencies, trust boundaries, and interfaces crossed by this architecture.",
1160
+ "Name what is deliberately outside the boundary and link neighboring architecture or contract docs when they own it."
1161
+ ]),
1162
+ sectionSpec("## Components", [
1163
+ "List the major runtime/build-time components, modules, services, jobs, or generated artifacts and their responsibilities.",
1164
+ "Keep the component list current and evidence-backed; avoid speculative target architecture."
1165
+ ]),
1166
+ sectionSpec("## Data And Control Flow", [
1167
+ "Describe important data movement, command/control paths, synchronization points, state ownership, and failure paths.",
1168
+ "Call out persistence, queues, caches, external calls, and security-sensitive transitions where relevant."
1169
+ ]),
1170
+ sectionSpec("## Ownership", [
1171
+ "Document team/module ownership, review responsibility, operational responsibility, and escalation paths if known.",
1172
+ "If ownership is inferred from codeowners, config, or repository structure, cite that evidence."
1173
+ ]),
1174
+ sectionSpec("## Cross-Cutting Constraints", [
1175
+ "Record active constraints such as security, privacy, reliability, performance, portability, maintainability, compliance, and cost.",
1176
+ "Tie constraints to source evidence, tests, standards, or operational requirements where available."
1177
+ ])
1039
1178
  ]);
1040
1179
  };
1041
1180
  var renderWorkflowDocTemplateFile = () => {
1042
1181
  return renderTypedTruthDocTemplate("workflow", "behavior", "{{title}}", [
1043
- "## Triggers",
1044
- "## Inputs",
1045
- "## Execution Model",
1046
- "## Steps",
1047
- "## State, Retry, And Failure Behavior",
1048
- "## Outputs"
1182
+ sectionSpec("## Triggers", [
1183
+ "List events, commands, schedules, user actions, webhooks, or dependency signals that start this workflow.",
1184
+ "Include preconditions, authorization requirements, debounce/coalescing behavior, and disabled states when applicable."
1185
+ ]),
1186
+ sectionSpec("## Inputs", [
1187
+ "Document data, files, config, context, credentials, and environmental assumptions consumed by the workflow.",
1188
+ "Include validation, defaults, and normalization that happen before execution."
1189
+ ]),
1190
+ sectionSpec("## Execution Model", [
1191
+ "Describe synchronous/asynchronous execution, concurrency, locking, leases, batching, ordering, and idempotency behavior.",
1192
+ "State whether the workflow is user-blocking, background, distributed, or delegated to another system."
1193
+ ]),
1194
+ sectionSpec("## Steps", [
1195
+ "Capture the current ordered steps or phases at a level useful for maintenance and review.",
1196
+ "Reference implementation entrypoints instead of duplicating line-by-line code behavior."
1197
+ ]),
1198
+ sectionSpec("## State, Retry, And Failure Behavior", [
1199
+ "Document state transitions, retries, timeouts, compensation, fallback, partial-success, and terminal-failure behavior.",
1200
+ "Make externally visible failure semantics and recovery responsibilities clear."
1201
+ ]),
1202
+ sectionSpec("## Outputs", [
1203
+ "List artifacts, state changes, notifications, logs, metrics, diagnostics, and downstream triggers produced by the workflow.",
1204
+ "Include success criteria and handoff points to other truth docs or systems."
1205
+ ])
1049
1206
  ]);
1050
1207
  };
1051
1208
  var renderOperationsDocTemplateFile = () => {
1052
1209
  return renderTypedTruthDocTemplate("operations", "behavior", "{{title}}", [
1053
- "## Operational Surface",
1054
- "## Runtime Topology",
1055
- "## Configuration",
1056
- "## Permissions",
1057
- "## Deployment And Rollback",
1058
- "## Availability And Observability"
1210
+ sectionSpec("## Operational Surface", [
1211
+ "Describe what operators, maintainers, or automated systems can observe or control for this surface.",
1212
+ "Include commands, dashboards, alerts, runbooks, jobs, or operational APIs that define current operations."
1213
+ ]),
1214
+ sectionSpec("## Runtime Topology", [
1215
+ "Document services, processes, containers, hosts, regions, dependencies, queues, stores, and network boundaries involved at runtime.",
1216
+ "State single-node/local behavior explicitly when there is no distributed topology."
1217
+ ]),
1218
+ sectionSpec("## Configuration", [
1219
+ "List operational config, environment variables, feature flags, secrets references, defaults, and reload/restart requirements.",
1220
+ "Do not include secret values; describe storage and rotation expectations instead."
1221
+ ]),
1222
+ sectionSpec("## Permissions", [
1223
+ "Document required identities, roles, scopes, filesystem/network permissions, and least-privilege boundaries.",
1224
+ "Include user-facing authorization behavior and operator access requirements when relevant."
1225
+ ]),
1226
+ sectionSpec("## Deployment And Rollback", [
1227
+ "Describe deployment mechanism, migration ordering, compatibility windows, rollback path, and known irreversible operations.",
1228
+ "Call out manual gates, smoke checks, and post-deploy verification responsibilities."
1229
+ ]),
1230
+ sectionSpec("## Availability And Observability", [
1231
+ "Capture availability expectations, health checks, metrics, logs, traces, alerts, SLO/error-budget signals, and known blind spots.",
1232
+ "Include what maintainers should inspect first during incidents or degraded behavior."
1233
+ ])
1059
1234
  ]);
1060
1235
  };
1061
1236
  var renderTestBehaviorDocTemplateFile = () => {
1062
1237
  return renderTypedTruthDocTemplate("test-behavior", "behavior", "{{title}}", [
1063
- "## Test Surface",
1064
- "## Fixtures And Data Model",
1065
- "## Execution Model",
1066
- "## Assertions And Invariants",
1067
- "## Isolation Rules",
1068
- "## Reporting And Failure Semantics"
1238
+ sectionSpec("## Test Surface", [
1239
+ "Define the behavior, contract, architecture, or workflow surface these tests verify.",
1240
+ "Link the canonical truth docs and code paths the tests are meant to protect."
1241
+ ]),
1242
+ sectionSpec("## Fixtures And Data Model", [
1243
+ "Document fixtures, factories, seeds, mocks/fakes, test repositories, external-service substitutes, and data lifecycle rules.",
1244
+ "Include cleanup, determinism, privacy, and cross-test contamination constraints."
1245
+ ]),
1246
+ sectionSpec("## Execution Model", [
1247
+ "Describe how tests run: command, framework, parallelism, isolation, network/filesystem assumptions, and required services.",
1248
+ "State whether tests are unit, integration, e2e, contract, smoke, regression, or generated checks."
1249
+ ]),
1250
+ sectionSpec("## Assertions And Invariants", [
1251
+ "List the critical assertions, invariants, failure modes, and negative cases that make the tests meaningful.",
1252
+ "Tie assertions to product/contract rules rather than incidental implementation details."
1253
+ ]),
1254
+ sectionSpec("## Isolation Rules", [
1255
+ "Document transaction boundaries, temp directories, fake clocks, network blocking, shared resources, and teardown rules.",
1256
+ "Call out known order dependencies or flake risks and how they are controlled."
1257
+ ]),
1258
+ sectionSpec("## Reporting And Failure Semantics", [
1259
+ "Describe diagnostics, snapshots, logs, coverage signals, retry policy, and how maintainers should interpret failures.",
1260
+ "Include escalation or quarantine criteria for flaky or environment-sensitive tests."
1261
+ ])
1069
1262
  ]);
1070
1263
  };
1071
1264
  var renderTemplate = (template, values) => {
@@ -1074,12 +1267,12 @@ var renderTemplate = (template, values) => {
1074
1267
  }, template);
1075
1268
  };
1076
1269
  var renderBehaviorLeafDocTemplate = (config, template = renderBehaviorDocTemplateFile()) => {
1077
- const defaultArea = config.docs.routing.defaultArea;
1270
+ const defaultArea = config.truthmark.routes.defaultArea;
1078
1271
  const title = titleCase(defaultArea);
1079
1272
  const templatePath = `${truthRoot(config)}/${defaultArea}/overview.md`;
1080
1273
  const sourceOfTruth = resolveRelativePath(
1081
1274
  templatePath,
1082
- `${config.docs.routing.areaFilesRoot}/${defaultArea}.md`
1275
+ `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`
1083
1276
  );
1084
1277
  const today = currentDate();
1085
1278
  return renderTemplate(template, {
@@ -1187,18 +1380,15 @@ import { Ajv } from "ajv";
1187
1380
  import { parse as parse2 } from "yaml";
1188
1381
  var ajv = new Ajv({ allErrors: true });
1189
1382
  var validateTruthmarkConfig = ajv.compile(truthmarkConfigSchema);
1190
- var toConfigDiagnostic = (message, file) => {
1191
- return {
1192
- category: "config",
1193
- severity: "error",
1194
- message,
1195
- file
1196
- };
1197
- };
1383
+ var toConfigDiagnostic = (message, file) => ({
1384
+ category: "config",
1385
+ severity: "error",
1386
+ message,
1387
+ file
1388
+ });
1198
1389
  var normalizeRepoRelativePath = (value) => {
1199
1390
  const slashNormalized = value.replace(/\\/gu, "/");
1200
- const pathNormalized = path4.posix.normalize(slashNormalized).replace(/\/+$/u, "");
1201
- return pathNormalized;
1391
+ return path4.posix.normalize(slashNormalized).replace(/\/+$/u, "");
1202
1392
  };
1203
1393
  var isUnsafeRepoRelativePath = (value) => {
1204
1394
  const slashNormalized = value.replace(/\\/gu, "/");
@@ -1206,85 +1396,153 @@ var isUnsafeRepoRelativePath = (value) => {
1206
1396
  const parts = slashNormalized.split("/");
1207
1397
  return normalized.length === 0 || normalized === "." || normalized === ".." || path4.isAbsolute(value) || path4.posix.isAbsolute(slashNormalized) || path4.win32.isAbsolute(value) || /^[A-Za-z]:/u.test(value) || normalized.startsWith("../") || parts.includes("..");
1208
1398
  };
1399
+ var joinWorkspacePath = (workspace, childPath) => {
1400
+ return normalizeRepoRelativePath(`${workspace}/${childPath}`);
1401
+ };
1402
+ var portalOutputFor = (workspace) => joinWorkspacePath(workspace, "generated/portal");
1403
+ var portalTemplateFor = (templatesRoot) => joinWorkspacePath(templatesRoot, "portal.html");
1209
1404
  var pathsOverlap = (left, right) => {
1210
1405
  const normalizedLeft = normalizeRepoRelativePath(left);
1211
1406
  const normalizedRight = normalizeRepoRelativePath(right);
1212
1407
  return normalizedLeft === normalizedRight || normalizedLeft.startsWith(`${normalizedRight}/`) || normalizedRight.startsWith(`${normalizedLeft}/`);
1213
1408
  };
1214
- var portalForbiddenOutputRoots = (rawConfig) => {
1215
- const rawDocs = rawConfig.docs;
1216
- const docsRoots = rawDocs?.roots ?? {};
1217
- const routing = rawDocs?.routing ?? DEFAULT_DOCS_HIERARCHY.routing;
1218
- return [
1219
- "src",
1220
- DEFAULT_DOCS_HIERARCHY.roots.ai,
1221
- DEFAULT_DOCS_HIERARCHY.roots.standards,
1222
- DEFAULT_DOCS_HIERARCHY.roots.architecture,
1223
- DEFAULT_DOCS_HIERARCHY.roots.truth,
1224
- ...Object.values(docsRoots),
1225
- routing.root_index,
1226
- routing.area_files_root,
1227
- ".truthmark/config.yml",
1228
- "AGENTS.md",
1229
- "CLAUDE.md",
1230
- "GEMINI.md",
1231
- ".github/copilot-instructions.md",
1232
- ...rawConfig.instruction_targets ?? DEFAULT_INSTRUCTION_TARGETS
1233
- ];
1234
- };
1235
- var validatePortalConfig = (rawConfig, configPath) => {
1236
- const portal = rawConfig["truthmark-portal"];
1237
- if (portal === void 0) {
1409
+ var CONFIG_PATH2 = ".truthmark/config.yml";
1410
+ var FORBIDDEN_WORKSPACE_OVERLAPS = [
1411
+ ".git",
1412
+ ".truthmark",
1413
+ "package.json",
1414
+ "package-lock.json",
1415
+ "pnpm-lock.yaml",
1416
+ "yarn.lock",
1417
+ "src",
1418
+ "tests"
1419
+ ];
1420
+ var unsupportedShapeDiagnostics = (parsedConfig, configPath) => {
1421
+ if (!parsedConfig || typeof parsedConfig !== "object" || Array.isArray(parsedConfig)) {
1238
1422
  return [];
1239
1423
  }
1424
+ const record = parsedConfig;
1240
1425
  const diagnostics = [];
1241
- const output = portal.output ?? DEFAULT_TRUTHMARK_PORTAL.output;
1242
- const template = portal.template ?? DEFAULT_TRUTHMARK_PORTAL.template;
1243
- if (isUnsafeRepoRelativePath(output) || portalForbiddenOutputRoots(rawConfig).some((forbidden) => pathsOverlap(output, forbidden))) {
1426
+ if (record.version !== 2) {
1244
1427
  diagnostics.push(
1245
1428
  toConfigDiagnostic(
1246
- "truthmark-portal.output must be a non-empty repo-relative directory that does not overlap source, instruction, routing, or canonical docs roots.",
1429
+ "Unsupported Truthmark config shape. This release requires version: 2 with a truthmark workspace block.",
1247
1430
  configPath
1248
1431
  )
1249
1432
  );
1250
1433
  }
1251
- if (template !== "default" && isUnsafeRepoRelativePath(template)) {
1434
+ if ("docs" in record || "authority" in record) {
1252
1435
  diagnostics.push(
1253
1436
  toConfigDiagnostic(
1254
- "truthmark-portal.template must be 'default' or a non-empty repo-relative template path without absolute or parent traversal segments.",
1437
+ "Unsupported Truthmark config shape. Remove old docs.roots and legacy authority settings; use version: 2 truthmark.workspace paths.",
1255
1438
  configPath
1256
1439
  )
1257
1440
  );
1258
1441
  }
1259
1442
  return diagnostics;
1260
1443
  };
1444
+ var validateWorkspacePaths = (rawConfig, configPath) => {
1445
+ const diagnostics = [];
1446
+ 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
+ if (isUnsafeRepoRelativePath(rawConfig.truthmark.workspace) || FORBIDDEN_WORKSPACE_OVERLAPS.some((forbidden) => pathsOverlap(workspace, forbidden))) {
1454
+ diagnostics.push(
1455
+ toConfigDiagnostic(
1456
+ "truthmark.workspace must be a non-empty repo-relative directory that does not overlap repository control, package, source, test, or instruction paths.",
1457
+ configPath
1458
+ )
1459
+ );
1460
+ }
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
+ for (const target of rawConfig.instruction_targets ?? DEFAULT_INSTRUCTION_TARGETS) {
1489
+ if (isUnsafeRepoRelativePath(target) || pathsOverlap(workspace, target)) {
1490
+ diagnostics.push(
1491
+ toConfigDiagnostic(
1492
+ "instruction_targets must be repo-relative files outside truthmark.workspace.",
1493
+ configPath
1494
+ )
1495
+ );
1496
+ }
1497
+ }
1498
+ return diagnostics;
1499
+ };
1261
1500
  var normalizeConfig = (rawConfig) => {
1262
- const rawDocs = rawConfig.docs ?? {
1263
- layout: DEFAULT_DOCS_HIERARCHY.layout,
1264
- roots: { ...DEFAULT_DOCS_HIERARCHY.roots },
1265
- routing: { ...DEFAULT_DOCS_HIERARCHY.routing }
1266
- };
1267
- const roots = { ...DEFAULT_DOCS_HIERARCHY.roots, ...rawDocs.roots };
1501
+ 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);
1506
+ const portalOutput = portalOutputFor(workspace);
1507
+ const portalTemplate = portalTemplateFor(templatesRoot);
1268
1508
  return {
1269
1509
  version: rawConfig.version,
1270
1510
  platforms: rawConfig.platforms ?? [...DEFAULT_PLATFORMS],
1271
- docs: {
1272
- layout: rawDocs.layout,
1273
- roots,
1274
- routing: {
1275
- rootIndex: rawDocs.routing.root_index,
1276
- areaFilesRoot: rawDocs.routing.area_files_root,
1277
- defaultArea: rawDocs.routing.default_area,
1278
- maxDelegationDepth: rawDocs.routing.max_delegation_depth
1279
- }
1511
+ truthmark: {
1512
+ workspace,
1513
+ 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
1518
+ },
1519
+ truth: {
1520
+ root: normalizeRepoRelativePath(rawConfig.truthmark.truth.root)
1521
+ },
1522
+ templates: {
1523
+ root: normalizeRepoRelativePath(rawConfig.truthmark.templates.root)
1524
+ },
1525
+ generated: {
1526
+ portal: {
1527
+ enabled: rawConfig.truthmark.generated.portal.enabled
1528
+ }
1529
+ },
1530
+ paths: {
1531
+ routesIndex,
1532
+ routeAreasRoot,
1533
+ truthRoot: truthRoot3,
1534
+ templatesRoot,
1535
+ portalOutput,
1536
+ portalTemplate
1537
+ },
1538
+ controlledPaths: [
1539
+ routesIndex,
1540
+ `${routeAreasRoot}/**/*.md`,
1541
+ `${truthRoot3}/**/*.md`,
1542
+ `${templatesRoot}/*.md`
1543
+ ]
1280
1544
  },
1281
- authority: rawConfig.authority,
1282
1545
  instructionTargets: rawConfig.instruction_targets ?? [...DEFAULT_INSTRUCTION_TARGETS],
1283
- truthmarkPortal: {
1284
- enabled: rawConfig["truthmark-portal"]?.enabled ?? DEFAULT_TRUTHMARK_PORTAL.enabled,
1285
- output: rawConfig["truthmark-portal"]?.output ?? DEFAULT_TRUTHMARK_PORTAL.output,
1286
- template: rawConfig["truthmark-portal"]?.template ?? DEFAULT_TRUTHMARK_PORTAL.template
1287
- },
1288
1546
  frontmatter: {
1289
1547
  required: rawConfig.frontmatter?.required ?? [],
1290
1548
  recommended: rawConfig.frontmatter?.recommended ?? []
@@ -1293,8 +1551,7 @@ var normalizeConfig = (rawConfig) => {
1293
1551
  };
1294
1552
  };
1295
1553
  var loadConfig = async (rootDir) => {
1296
- const configPath = ".truthmark/config.yml";
1297
- const absolutePath = resolveRepoPath(rootDir, configPath);
1554
+ const absolutePath = resolveRepoPath(rootDir, CONFIG_PATH2);
1298
1555
  let source;
1299
1556
  try {
1300
1557
  source = await fs4.readFile(absolutePath, "utf8");
@@ -1303,8 +1560,8 @@ var loadConfig = async (rootDir) => {
1303
1560
  return {
1304
1561
  status: "missing",
1305
1562
  config: null,
1306
- diagnostics: [toConfigDiagnostic("Missing .truthmark/config.yml.", configPath)],
1307
- configPath
1563
+ diagnostics: [toConfigDiagnostic("Missing .truthmark/config.yml.", CONFIG_PATH2)],
1564
+ configPath: CONFIG_PATH2
1308
1565
  };
1309
1566
  }
1310
1567
  throw error;
@@ -1319,10 +1576,19 @@ var loadConfig = async (rootDir) => {
1319
1576
  diagnostics: [
1320
1577
  toConfigDiagnostic(
1321
1578
  `Invalid YAML: ${error instanceof Error ? error.message : String(error)}`,
1322
- configPath
1579
+ CONFIG_PATH2
1323
1580
  )
1324
1581
  ],
1325
- configPath
1582
+ configPath: CONFIG_PATH2
1583
+ };
1584
+ }
1585
+ const unsupportedDiagnostics = unsupportedShapeDiagnostics(parsedConfig, CONFIG_PATH2);
1586
+ if (unsupportedDiagnostics.length > 0) {
1587
+ return {
1588
+ status: "invalid",
1589
+ config: null,
1590
+ diagnostics: unsupportedDiagnostics,
1591
+ configPath: CONFIG_PATH2
1326
1592
  };
1327
1593
  }
1328
1594
  if (!validateTruthmarkConfig(parsedConfig)) {
@@ -1333,49 +1599,30 @@ var loadConfig = async (rootDir) => {
1333
1599
  const propertyPath = error.instancePath || "/";
1334
1600
  const additionalProperty = error.keyword === "additionalProperties" && error.params && "additionalProperty" in error.params ? String(error.params.additionalProperty) : null;
1335
1601
  const message = additionalProperty ? `${propertyPath} additional property ${additionalProperty} is not allowed` : `${propertyPath} ${error.message ?? "is invalid"}`.trim();
1336
- return toConfigDiagnostic(message, configPath);
1602
+ return toConfigDiagnostic(message, CONFIG_PATH2);
1337
1603
  }),
1338
- configPath
1604
+ configPath: CONFIG_PATH2
1339
1605
  };
1340
1606
  }
1341
- const portalDiagnostics = validatePortalConfig(
1342
- parsedConfig,
1343
- configPath
1344
- );
1345
- if (portalDiagnostics.length > 0) {
1607
+ const pathDiagnostics = validateWorkspacePaths(parsedConfig, CONFIG_PATH2);
1608
+ if (pathDiagnostics.length > 0) {
1346
1609
  return {
1347
1610
  status: "invalid",
1348
1611
  config: null,
1349
- diagnostics: portalDiagnostics,
1350
- configPath
1612
+ diagnostics: pathDiagnostics,
1613
+ configPath: CONFIG_PATH2
1351
1614
  };
1352
1615
  }
1353
1616
  return {
1354
1617
  status: "loaded",
1355
1618
  config: normalizeConfig(parsedConfig),
1356
1619
  diagnostics: [],
1357
- configPath
1620
+ configPath: CONFIG_PATH2
1358
1621
  };
1359
1622
  };
1360
1623
 
1361
1624
  // src/init/hierarchy.ts
1362
1625
  import fs5 from "fs/promises";
1363
- import fg from "fast-glob";
1364
- var KNOWN_DEFAULT_ROOTS = [
1365
- DEFAULT_DOCS_HIERARCHY.roots.truth,
1366
- "docs/api",
1367
- DEFAULT_DOCS_HIERARCHY.roots.architecture,
1368
- DEFAULT_DOCS_HIERARCHY.roots.standards,
1369
- "docs/guides"
1370
- ];
1371
- var hasMarkdownFiles = async (rootDir, root) => {
1372
- const matches = await fg([`${root}/**/*.md`], {
1373
- cwd: rootDir,
1374
- onlyFiles: true,
1375
- followSymbolicLinks: false
1376
- });
1377
- return matches.length > 0;
1378
- };
1379
1626
  var truthRoot2 = resolveTruthDocsRoot;
1380
1627
  var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePath) => {
1381
1628
  const rootIndexSource = await fs5.readFile(resolveRepoPath(rootDir, rootIndexPath), "utf8");
@@ -1384,9 +1631,15 @@ var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePat
1384
1631
  (areaReference) => areaReference.areaFiles.includes(childRoutePath)
1385
1632
  );
1386
1633
  };
1387
- var readBehaviorDocTemplate = async (rootDir) => {
1634
+ var truthTemplatePath = (config, fileName) => {
1635
+ return `${config.truthmark.paths.templatesRoot}/${fileName}`;
1636
+ };
1637
+ var readBehaviorDocTemplate = async (rootDir, config) => {
1388
1638
  try {
1389
- return await fs5.readFile(resolveRepoPath(rootDir, BEHAVIOR_DOC_TEMPLATE_PATH), "utf8");
1639
+ return await fs5.readFile(
1640
+ resolveRepoPath(rootDir, truthTemplatePath(config, "behavior-doc.md")),
1641
+ "utf8"
1642
+ );
1390
1643
  } catch (error) {
1391
1644
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1392
1645
  return renderBehaviorDocTemplateFile();
@@ -1394,19 +1647,28 @@ var readBehaviorDocTemplate = async (rootDir) => {
1394
1647
  throw error;
1395
1648
  }
1396
1649
  };
1650
+ var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTemplate) => {
1651
+ const seededResult = await ensureRepoFile(rootDir, templatePath, defaultTemplate);
1652
+ if (seededResult.status !== "unchanged") {
1653
+ return seededResult;
1654
+ }
1655
+ const existingTemplate = await fs5.readFile(resolveRepoPath(rootDir, templatePath), "utf8");
1656
+ const mergedTemplate = mergeTruthDocTemplate(existingTemplate, defaultTemplate);
1657
+ return writeRepoFile(rootDir, templatePath, mergedTemplate);
1658
+ };
1397
1659
  var scaffoldHierarchy = async (rootDir, config) => {
1398
1660
  const results = [];
1399
1661
  const truthDocsRoot = truthRoot2(config);
1400
- const truthDomainRoot = `${truthDocsRoot}/${config.docs.routing.defaultArea}`;
1401
- const childRoutePath = `${config.docs.routing.areaFilesRoot}/${config.docs.routing.defaultArea}.md`;
1662
+ const truthDomainRoot = `${truthDocsRoot}/${config.truthmark.routes.defaultArea}`;
1663
+ const childRoutePath = `${config.truthmark.paths.routeAreasRoot}/${config.truthmark.routes.defaultArea}.md`;
1402
1664
  results.push(
1403
1665
  await ensureRepoFile(
1404
1666
  rootDir,
1405
- config.docs.routing.rootIndex,
1667
+ config.truthmark.paths.routesIndex,
1406
1668
  renderHierarchicalAreasIndexTemplate(config)
1407
1669
  )
1408
1670
  );
1409
- if (await rootIndexReferencesChildRoute(rootDir, config.docs.routing.rootIndex, childRoutePath)) {
1671
+ if (await rootIndexReferencesChildRoute(rootDir, config.truthmark.paths.routesIndex, childRoutePath)) {
1410
1672
  results.push(await ensureRepoFile(rootDir, childRoutePath, renderChildAreaTemplate(config)));
1411
1673
  }
1412
1674
  results.push(
@@ -1424,32 +1686,48 @@ var scaffoldHierarchy = async (rootDir, config) => {
1424
1686
  )
1425
1687
  );
1426
1688
  results.push(
1427
- await ensureRepoFile(rootDir, BEHAVIOR_DOC_TEMPLATE_PATH, renderBehaviorDocTemplateFile())
1689
+ await ensureOrUpdateTruthDocTemplate(
1690
+ rootDir,
1691
+ truthTemplatePath(config, "behavior-doc.md"),
1692
+ renderBehaviorDocTemplateFile()
1693
+ )
1428
1694
  );
1429
1695
  results.push(
1430
- await ensureRepoFile(rootDir, CONTRACT_DOC_TEMPLATE_PATH, renderContractDocTemplateFile())
1696
+ await ensureOrUpdateTruthDocTemplate(
1697
+ rootDir,
1698
+ truthTemplatePath(config, "contract-doc.md"),
1699
+ renderContractDocTemplateFile()
1700
+ )
1431
1701
  );
1432
1702
  results.push(
1433
- await ensureRepoFile(
1703
+ await ensureOrUpdateTruthDocTemplate(
1434
1704
  rootDir,
1435
- ARCHITECTURE_DOC_TEMPLATE_PATH,
1705
+ truthTemplatePath(config, "architecture-doc.md"),
1436
1706
  renderArchitectureDocTemplateFile()
1437
1707
  )
1438
1708
  );
1439
1709
  results.push(
1440
- await ensureRepoFile(rootDir, WORKFLOW_DOC_TEMPLATE_PATH, renderWorkflowDocTemplateFile())
1710
+ await ensureOrUpdateTruthDocTemplate(
1711
+ rootDir,
1712
+ truthTemplatePath(config, "workflow-doc.md"),
1713
+ renderWorkflowDocTemplateFile()
1714
+ )
1441
1715
  );
1442
1716
  results.push(
1443
- await ensureRepoFile(rootDir, OPERATIONS_DOC_TEMPLATE_PATH, renderOperationsDocTemplateFile())
1717
+ await ensureOrUpdateTruthDocTemplate(
1718
+ rootDir,
1719
+ truthTemplatePath(config, "operations-doc.md"),
1720
+ renderOperationsDocTemplateFile()
1721
+ )
1444
1722
  );
1445
1723
  results.push(
1446
- await ensureRepoFile(
1724
+ await ensureOrUpdateTruthDocTemplate(
1447
1725
  rootDir,
1448
- TEST_BEHAVIOR_DOC_TEMPLATE_PATH,
1726
+ truthTemplatePath(config, "test-behavior-doc.md"),
1449
1727
  renderTestBehaviorDocTemplateFile()
1450
1728
  )
1451
1729
  );
1452
- const behaviorDocTemplate = await readBehaviorDocTemplate(rootDir);
1730
+ const behaviorDocTemplate = await readBehaviorDocTemplate(rootDir, config);
1453
1731
  results.push(
1454
1732
  await ensureRepoFile(
1455
1733
  rootDir,
@@ -1459,24 +1737,6 @@ var scaffoldHierarchy = async (rootDir, config) => {
1459
1737
  );
1460
1738
  return results;
1461
1739
  };
1462
- var detectHierarchyMigrationDiagnostics = async (rootDir, config) => {
1463
- const configuredRoots = new Set(Object.values(config.docs.roots));
1464
- const diagnostics = [];
1465
- for (const defaultRoot of KNOWN_DEFAULT_ROOTS) {
1466
- if (configuredRoots.has(defaultRoot)) {
1467
- continue;
1468
- }
1469
- if (await hasMarkdownFiles(rootDir, defaultRoot)) {
1470
- diagnostics.push({
1471
- category: "config",
1472
- severity: "review",
1473
- message: `Configured hierarchy no longer includes ${defaultRoot}, but markdown still exists there. Perform manual migration before relying on the new hierarchy.`,
1474
- file: ".truthmark/config.yml"
1475
- });
1476
- }
1477
- }
1478
- return diagnostics;
1479
- };
1480
1740
 
1481
1741
  // src/truth/evidence.ts
1482
1742
  var renderClaimEvidenceCheckedSection = (items) => {
@@ -1521,11 +1781,12 @@ var REPOSITORY_INTELLIGENCE_INSTRUCTIONS = [
1521
1781
  "If unavailable, inspect any present Truthmark config, route files, source files, truth docs, and tests directly, then report that repository-intelligence artifacts were not generated."
1522
1782
  ].join("\n");
1523
1783
  var FEATURE_DOC_TEMPLATE_INSTRUCTIONS = [
1524
- "When creating or updating a truth doc, inspect the routed truth kind and use the matching `docs/templates/<kind>-doc.md` template.",
1784
+ "When creating or updating a truth doc, inspect the routed truth kind and use the matching template under the configured Truthmark templates root.",
1525
1785
  "Supported kinds: behavior, contract, architecture, workflow, operations, and test-behavior.",
1526
- "Align existing docs to that template while preserving accurate authored content.",
1786
+ "Treat the HTML comments under each template section as normative authoring guidance for that section.",
1787
+ "Align existing docs to that template and write or repair section content so it satisfies the comment guidance while preserving accurate authored content.",
1527
1788
  "If the template is missing, use Scope, Product Decisions, Rationale, and the kind-specific current-truth section.",
1528
- "Teams may edit the template files under docs/templates/ to define their local truth-doc standards."
1789
+ "Teams may edit template files under the configured Truthmark templates root to define their local truth-doc standards."
1529
1790
  ].join("\n");
1530
1791
  var renderTruthDocOwnershipGateSection = (subject, outcome) => {
1531
1792
  return [
@@ -1699,8 +1960,8 @@ var renderHierarchySummary = (config) => {
1699
1960
  return [
1700
1961
  "Truthmark hierarchy hints:",
1701
1962
  "- Config, when present: .truthmark/config.yml",
1702
- `- Root route index, when present: ${config.docs.routing.rootIndex}`,
1703
- `- Area route files, when present: ${config.docs.routing.areaFilesRoot}/**/*.md`,
1963
+ `- Root route index, when present: ${config.truthmark.paths.routesIndex}`,
1964
+ `- Area route files, when present: ${config.truthmark.paths.routeAreasRoot}/**/*.md`,
1704
1965
  `- Truth docs, when present: ${truthRoot3}/**/*.md`
1705
1966
  ].join("\n");
1706
1967
  };
@@ -1717,10 +1978,10 @@ var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
1717
1978
  var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
1718
1979
  var renderCompactHierarchySummary = (config) => {
1719
1980
  const truthRoot3 = resolveTruthDocsRoot(config);
1720
- return `Hierarchy hints: config .truthmark/config.yml when present; routes ${config.docs.routing.rootIndex} and ${config.docs.routing.areaFilesRoot}/**/*.md when present; Truth docs: ${truthRoot3}/**/*.md when present.`;
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.`;
1721
1982
  };
1722
1983
  var renderAgentsBlock = (config = defaultAgentConfig()) => {
1723
- const portalLine = config.truthmarkPortal.enabled ? `Truthmark Portal is a separate manual-only presentation workflow. Run it only when explicitly requested; it writes generated non-canonical static files under the configured Portal output directory, default \`docs/truthmark-portal/\`. Markdown remains canonical.` : null;
1984
+ 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;
1724
1985
  return [
1725
1986
  TRUTHMARK_BLOCK_START,
1726
1987
  "## Truthmark Workflow",
@@ -1740,70 +2001,6 @@ var renderAgentsBlock = (config = defaultAgentConfig()) => {
1740
2001
  ].join("\n");
1741
2002
  };
1742
2003
 
1743
- // src/templates/default-standards.ts
1744
- var DEFAULT_STANDARDS = [
1745
- {
1746
- path: "docs/standards/default-principles.md",
1747
- content: `---
1748
- status: active
1749
- doc_type: standard
1750
- last_reviewed: 2026-05-03
1751
- source_of_truth:
1752
- - README.md
1753
- ---
1754
-
1755
- # Default Principles
1756
-
1757
- ## Scope
1758
-
1759
- This is a bootstrap standards baseline for repositories that adopt Truthmark.
1760
-
1761
- ## Reusable Defaults
1762
-
1763
- - Authority order should be explicit.
1764
- - Committed repository artifacts are the durable source of truth.
1765
- - Each document should have one primary responsibility.
1766
- - Each class of fact should have one canonical source.
1767
- - Architecture docs describe system structure, module boundaries, runtime topology, persistence boundaries, cross-cutting contracts, and generated-surface ownership.
1768
- - Do not put ordinary feature behavior in architecture docs.
1769
- - Verification should be explicit, and skipped checks should state why.
1770
- - Missing, stale, broad, overloaded, or unrouteable documentation topology should be repaired through AI-native structure workflow before agents create more generic truth docs.
1771
- - Installed repository workflows should remain usable from committed files even when the Truthmark CLI is unavailable.
1772
- `
1773
- },
1774
- {
1775
- path: "docs/standards/documentation-governance.md",
1776
- content: `---
1777
- status: active
1778
- doc_type: standard
1779
- last_reviewed: 2026-05-03
1780
- source_of_truth:
1781
- - README.md
1782
- ---
1783
-
1784
- # Documentation Governance
1785
-
1786
- ## Core Rules
1787
-
1788
- - Each document should have one primary responsibility.
1789
- - Each class of fact should have one canonical source.
1790
- - Current implementation, reusable standards, and future proposals should be stored separately.
1791
- - Generated helper output is never canonical truth.
1792
- - Architecture docs describe structure and ownership; truth docs describe current product behavior.
1793
-
1794
- ## Truthmark Implications
1795
-
1796
- - Truth Sync should extend mapped docs first, create an area-local doc second, and create a new area only as a last resort.
1797
- - Weak routing produces weak truth maintenance.
1798
- - Missing, stale, broad, overloaded, or unrouteable routing should trigger Truth Structure before more generic truth docs are created.
1799
- `
1800
- }
1801
- ];
1802
- var renderDefaultStandards = (documents) => {
1803
- const existingPaths = new Set(documents.map((document) => document.path));
1804
- return DEFAULT_STANDARDS.filter((template) => !existingPaths.has(template.path));
1805
- };
1806
-
1807
2004
  // src/templates/workflow-surfaces.ts
1808
2005
  import { stringify as stringify2 } from "yaml";
1809
2006
 
@@ -2087,7 +2284,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2087
2284
  "generate the Truthmark Portal",
2088
2285
  "refresh the committed HTML docs site",
2089
2286
  "create a browsable project map from Truthmark docs",
2090
- "update docs/truthmark-portal",
2287
+ "update the Truthmark Portal output",
2091
2288
  "make a human-readable static site from the truth docs"
2092
2289
  ],
2093
2290
  negativeTriggers: [
@@ -2101,7 +2298,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2101
2298
  forbiddenAdjacency: [
2102
2299
  "must not run as a completion gate",
2103
2300
  "must not replace Truth Sync, Truth Check, Truth Document, Truth Realize, or Truth Structure",
2104
- "must not write outside the configured Portal output directory unless the user changes scope"
2301
+ "must not write outside the fixed Portal output directory"
2105
2302
  ],
2106
2303
  requiredGates: [
2107
2304
  "manual-only invocation",
@@ -2109,7 +2306,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2109
2306
  "Markdown canonical statement",
2110
2307
  "source provenance"
2111
2308
  ],
2112
- allowedWrites: ["configured Portal output directory only"],
2309
+ allowedWrites: ["fixed Portal output directory only"],
2113
2310
  reportSections: [
2114
2311
  "Output path",
2115
2312
  "Page count",
@@ -2134,7 +2331,7 @@ var renderMarkdownExample = (content) => {
2134
2331
  };
2135
2332
  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.";
2136
2333
  var renderTruthCheckReportExample = (config = defaultAgentConfig()) => {
2137
- const rootRouteIndex = config.docs.routing.rootIndex;
2334
+ const rootRouteIndex = config.truthmark.paths.routesIndex;
2138
2335
  return `Truth Check: completed
2139
2336
 
2140
2337
  Files reviewed:
@@ -2203,7 +2400,7 @@ Truth Check is agent-led:
2203
2400
 
2204
2401
  - inspect .truthmark/config.yml and configured route files only when they exist; then inspect canonical docs and relevant implementation directly
2205
2402
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2206
- - inspect the configured root route index at ${config.docs.routing.rootIndex} and relevant child route files under ${config.docs.routing.areaFilesRoot}/ when they exist
2403
+ - inspect the configured root route index at ${config.truthmark.paths.routesIndex} and relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ when they exist
2207
2404
  - check that current docs describe current code rather than historical plans
2208
2405
  - check that route files map code surfaces to canonical truth docs when route files exist
2209
2406
  - check for broad, catch-all, index-like, or mixed-owner truth docs and report them as topology issues requiring Truth Structure
@@ -2236,7 +2433,7 @@ Implementation reviewed:
2236
2433
  - src/routing/area-resolver.ts
2237
2434
 
2238
2435
  Ownership reviewed:
2239
- - ${config.docs.routing.rootIndex}
2436
+ - ${config.truthmark.paths.routesIndex}
2240
2437
 
2241
2438
  Truth docs created:
2242
2439
  - ${truthDocsRoot}/contracts.md
@@ -2248,14 +2445,14 @@ Truth docs restructured:
2248
2445
  - ${truthDocsRoot}/check-diagnostics.md
2249
2446
 
2250
2447
  Routing updated:
2251
- - ${config.docs.routing.rootIndex}
2448
+ - ${config.truthmark.paths.routesIndex}
2252
2449
 
2253
2450
  ${renderClaimEvidenceCheckedSection([
2254
2451
  {
2255
2452
  claim: "Route resolution behavior is documented in the contracts truth doc.",
2256
2453
  evidence: [
2257
2454
  "src/routing/area-resolver.ts:14",
2258
- `${config.docs.routing.rootIndex}:9`
2455
+ `${config.truthmark.paths.routesIndex}:9`
2259
2456
  ],
2260
2457
  result: "supported"
2261
2458
  }
@@ -2313,7 +2510,7 @@ Truth Document is manual and implementation-first:
2313
2510
  - inspect .truthmark/config.yml and configured route files only when they exist; then inspect existing canonical docs, implementation code, and tests directly
2314
2511
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2315
2512
  - document current implemented behavior; do not invent future behavior or planned endpoints
2316
- - may write canonical truth docs and ${config.docs.routing.rootIndex} or relevant child route files only
2513
+ - may write canonical truth docs and ${config.truthmark.paths.routesIndex} or relevant child route files only
2317
2514
  - must not write functional code
2318
2515
  - when routing is missing, stale, broad, overloaded, catch-all, or cannot map the behavior to a bounded truth owner, run Truth Structure first when routing repair is safe and in scope
2319
2516
  - block and recommend Truth Structure when routing repair is unsafe, ambiguous, or outside the task boundary
@@ -2376,7 +2573,7 @@ Why this workflow:
2376
2573
  - forbidden adjacency considered: must not edit functional code
2377
2574
 
2378
2575
  Likely route owner:
2379
- - route file: ${config.docs.routing.rootIndex}
2576
+ - route file: ${config.truthmark.paths.routesIndex}
2380
2577
  - truth doc: ${truthDocsRoot}/example.md
2381
2578
  - confidence: medium
2382
2579
 
@@ -2420,8 +2617,8 @@ Purpose:
2420
2617
 
2421
2618
  Read:
2422
2619
  - .truthmark/config.yml, only when present
2423
- - ${config.docs.routing.rootIndex}, only when present
2424
- - relevant child route files under ${config.docs.routing.areaFilesRoot}/, only when present
2620
+ - ${config.truthmark.paths.routesIndex}, only when present
2621
+ - relevant child route files under ${config.truthmark.paths.routeAreasRoot}/, only when present
2425
2622
  - relevant truth docs and implementation files needed to preview ownership
2426
2623
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2427
2624
 
@@ -2450,12 +2647,12 @@ ${renderMarkdownExample3(renderTruthPreviewReportExample(config))}`;
2450
2647
  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.";
2451
2648
  var renderTruthmarkPortalSkillBody = (config = defaultAgentConfig()) => {
2452
2649
  const workflow = getTruthmarkWorkflow("truthmark-portal");
2453
- const output = config.truthmarkPortal.output;
2454
- const template = config.truthmarkPortal.template;
2650
+ const output = config.truthmark.paths.portalOutput;
2651
+ const template = config.truthmark.paths.portalTemplate;
2455
2652
  return `---
2456
2653
  name: truthmark-portal
2457
2654
  description: ${workflow.description}
2458
- argument-hint: Optional output path, template, or portal generation focus
2655
+ argument-hint: Optional portal generation focus
2459
2656
  user-invocable: true
2460
2657
  truthmark-version: ${TRUTHMARK_VERSION}
2461
2658
  ---
@@ -2471,9 +2668,9 @@ Core rules:
2471
2668
  - Markdown remains canonical; generated HTML is presentation only.
2472
2669
  - Read Markdown directly from the checkout; the workflow does not require the truthmark CLI or package.
2473
2670
  - truthmark check/index may be used only as optional supporting evidence when available.
2474
- - Default output is docs/truthmark-portal; configured output is ${output}.
2475
- - Configured template is ${template}; use default built-in template instructions when the template is default.
2476
- - The workflow may replace the entire output directory, but writes are limited to the configured Portal output directory only unless the user changes scope.
2671
+ - Determined Portal output is ${output}.
2672
+ - Determined Portal template path is ${template}; use built-in template instructions if that file is absent.
2673
+ - The workflow may replace the entire output directory, but writes are limited to the fixed Portal output directory only.
2477
2674
  - Portal writes are generated non-canonical static files for human browsing.
2478
2675
  - Generate a committed multi-page static HTML site with local CSS, JavaScript, assets, and search metadata under the output directory.
2479
2676
  - Use no remote dependencies by default: no remote scripts, analytics, fonts, CSS, or CDN assets.
@@ -2485,8 +2682,8 @@ Core rules:
2485
2682
  Workflow:
2486
2683
 
2487
2684
  1. Confirm the user explicitly requested Portal generation or refresh.
2488
- 2. Inspect .truthmark/config.yml and configured route docs only when they exist; read repository instruction files when present, truth docs, architecture docs, standards docs, and the configured Portal template when it is a repo-relative file.
2489
- 3. Validate the selected output path is repo-relative, non-empty, inside the repository, and does not overlap canonical docs, source roots, routing files, or instruction targets.
2685
+ 2. Inspect .truthmark/config.yml and configured route docs only when they exist; read repository instruction files when present, truth docs, architecture docs, standards docs, and the determined Portal template when present.
2686
+ 3. Validate the determined output path is repo-relative, non-empty, inside the repository, and does not overlap canonical docs, source roots, routing files, or instruction targets.
2490
2687
  4. Plan the generated page inventory, diagrams/assets, source docs reviewed, and skipped or ambiguous docs.
2491
2688
  5. Replace or write only under ${output}; do not edit canonical Markdown, routing, source code, or instruction files unless the user explicitly changes scope.
2492
2689
  6. Generate the multi-page static site with local assets/search metadata and visible source provenance.
@@ -2533,11 +2730,11 @@ var renderTruthStructureReportExample = (config = defaultAgentConfig()) => {
2533
2730
  Topology reviewed:
2534
2731
  - controllers: src/auth/**
2535
2732
  - docs root: ${truthDocsRoot}
2536
- - route files: ${config.docs.routing.rootIndex}
2733
+ - route files: ${config.truthmark.paths.routesIndex}
2537
2734
  Areas reviewed:
2538
2735
  - src/auth/**
2539
2736
  Routing updated:
2540
- - ${config.docs.routing.rootIndex}
2737
+ - ${config.truthmark.paths.routesIndex}
2541
2738
  Initial truth boundary:
2542
2739
  - Area: Authentication
2543
2740
  - Code: src/auth/**
@@ -2552,7 +2749,7 @@ Truth docs restructured:
2552
2749
  ${renderClaimEvidenceCheckedSection([
2553
2750
  {
2554
2751
  claim: "Session behavior belongs to a dedicated Authentication truth owner.",
2555
- evidence: ["src/auth/**", `${config.docs.routing.rootIndex}:7`],
2752
+ evidence: ["src/auth/**", `${config.truthmark.paths.routesIndex}:7`],
2556
2753
  result: "supported"
2557
2754
  }
2558
2755
  ])}
@@ -2588,15 +2785,15 @@ Invocations: ${TRUTH_STRUCTURE_EXPLICIT_INVOCATIONS}
2588
2785
  Truth Structure is agent-native:
2589
2786
  - inspect repository layout, current docs, Truthmark config and route files when present, and relevant code directly
2590
2787
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2591
- - inspect the configured root route index at ${config.docs.routing.rootIndex} and relevant child route files under ${config.docs.routing.areaFilesRoot}/ when they exist
2788
+ - inspect the configured root route index at ${config.truthmark.paths.routesIndex} and relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ when they exist
2592
2789
  - define areas by product or behavior ownership, not by mechanical directory mirroring
2593
- - create or repair ${config.docs.routing.rootIndex}
2790
+ - create or repair ${config.truthmark.paths.routesIndex}
2594
2791
  - create starter truth docs when useful and when they belong in the canonical current-truth surface
2595
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.
2596
2793
  - Starter truth docs must include ## Product Decisions and ## Rationale sections.
2597
2794
  ${subagentMode}
2598
2795
  ${FEATURE_DOC_TEMPLATE_INSTRUCTIONS}
2599
- - use ${truthDocsRoot}/**, docs/architecture/**, or docs/standards/** for current truth destinations
2796
+ - use ${truthDocsRoot}/** for current truth destinations
2600
2797
  - use only canonical current-truth destinations for starter truth docs
2601
2798
  - keep active Product Decisions and Rationale in the canonical doc that owns the behavior
2602
2799
  - preserve unrelated authored content
@@ -2631,7 +2828,7 @@ Topology pressure signals:
2631
2828
  - the configured truth root has many direct non-index docs
2632
2829
  - a changed controller, route, or service cannot map to a specific behavior doc
2633
2830
  - Truth Sync would need to create a new generic truth doc because routing is too broad
2634
- - endpoint or controller names reveal domains missing from ${config.docs.routing.areaFilesRoot}/**
2831
+ - endpoint or controller names reveal domains missing from ${config.truthmark.paths.routeAreasRoot}/**
2635
2832
  Use these review thresholds as guidance:
2636
2833
  - more than 10 direct truth docs in one folder
2637
2834
  - more than 15 leaf areas in one child route file
@@ -2640,7 +2837,7 @@ Use these review thresholds as guidance:
2640
2837
  Repair rules:
2641
2838
  - split broad, overloaded, or catch-all areas into behavior-owned child route files
2642
2839
  - split mixed-owner truth docs into bounded owner docs before adding new behavior claims
2643
- - create route files under ${config.docs.routing.areaFilesRoot}/ when a product/domain boundary is clear
2840
+ - create route files under ${config.truthmark.paths.routeAreasRoot}/ when a product/domain boundary is clear
2644
2841
  - create behavior truth docs under the configured truth root only when behavior lacks a current doc
2645
2842
  - README.md files are indexes, not Truth Sync targets
2646
2843
  - prefer bounded leaf truth docs at <truth-root>/<domain>/<behavior>.md
@@ -2879,12 +3076,12 @@ Report completion in this shape:
2879
3076
  ${renderMarkdownExample5(
2880
3077
  renderTruthSyncCompletedReport({
2881
3078
  changedCode: ["src/auth/session.ts"],
2882
- ownershipReviewed: [config.docs.routing.rootIndex],
3079
+ ownershipReviewed: [config.truthmark.paths.routesIndex],
2883
3080
  truthDocsUpdated: [`${truthDocsRoot}/repository/overview.md`],
2884
3081
  evidenceChecked: [
2885
3082
  {
2886
3083
  claim: "Session timeout behavior is documented in the mapped repository truth doc.",
2887
- evidence: ["src/auth/session.ts:12", `${config.docs.routing.rootIndex}:11`],
3084
+ evidence: ["src/auth/session.ts:12", `${config.truthmark.paths.routesIndex}:11`],
2888
3085
  result: "supported"
2889
3086
  }
2890
3087
  ],
@@ -2896,7 +3093,7 @@ Blocked report example:
2896
3093
  ${renderMarkdownExample5(
2897
3094
  renderTruthSyncBlockedReport({
2898
3095
  reason: "routing repair is not allowed",
2899
- manualReviewFiles: [config.docs.routing.rootIndex],
3096
+ manualReviewFiles: [config.truthmark.paths.routesIndex],
2900
3097
  nextAction: "update routing metadata and rerun Truth Sync"
2901
3098
  })
2902
3099
  )}`;
@@ -2919,20 +3116,20 @@ var TRUTHMARK_WRITE_WORKER_REPORT_FIELDS = [
2919
3116
  ];
2920
3117
 
2921
3118
  // src/templates/workflow-surfaces.ts
2922
- var TRUTHMARK_STRUCTURE_SKILL_PATH = ".codex/skills/truthmark-structure/SKILL.md";
2923
- var TRUTHMARK_STRUCTURE_SKILL_METADATA_PATH = ".codex/skills/truthmark-structure/agents/openai.yaml";
2924
- var TRUTHMARK_DOCUMENT_SKILL_PATH = ".codex/skills/truthmark-document/SKILL.md";
2925
- var TRUTHMARK_DOCUMENT_SKILL_METADATA_PATH = ".codex/skills/truthmark-document/agents/openai.yaml";
2926
- var TRUTHMARK_SYNC_SKILL_PATH = ".codex/skills/truthmark-sync/SKILL.md";
2927
- var TRUTHMARK_SYNC_SKILL_METADATA_PATH = ".codex/skills/truthmark-sync/agents/openai.yaml";
2928
- var TRUTHMARK_REALIZE_SKILL_PATH = ".codex/skills/truthmark-realize/SKILL.md";
2929
- var TRUTHMARK_REALIZE_SKILL_METADATA_PATH = ".codex/skills/truthmark-realize/agents/openai.yaml";
2930
- var TRUTHMARK_CHECK_SKILL_PATH = ".codex/skills/truthmark-check/SKILL.md";
2931
- var TRUTHMARK_CHECK_SKILL_METADATA_PATH = ".codex/skills/truthmark-check/agents/openai.yaml";
2932
- var TRUTHMARK_PREVIEW_SKILL_PATH = ".codex/skills/truthmark-preview/SKILL.md";
2933
- var TRUTHMARK_PREVIEW_SKILL_METADATA_PATH = ".codex/skills/truthmark-preview/agents/openai.yaml";
2934
- var TRUTHMARK_PORTAL_SKILL_PATH = ".codex/skills/truthmark-portal/SKILL.md";
2935
- var TRUTHMARK_PORTAL_SKILL_METADATA_PATH = ".codex/skills/truthmark-portal/agents/openai.yaml";
3119
+ var TRUTHMARK_STRUCTURE_SKILL_PATH = ".agents/skills/truthmark-structure/SKILL.md";
3120
+ var TRUTHMARK_STRUCTURE_SKILL_METADATA_PATH = ".agents/skills/truthmark-structure/agents/openai.yaml";
3121
+ var TRUTHMARK_DOCUMENT_SKILL_PATH = ".agents/skills/truthmark-document/SKILL.md";
3122
+ var TRUTHMARK_DOCUMENT_SKILL_METADATA_PATH = ".agents/skills/truthmark-document/agents/openai.yaml";
3123
+ var TRUTHMARK_SYNC_SKILL_PATH = ".agents/skills/truthmark-sync/SKILL.md";
3124
+ var TRUTHMARK_SYNC_SKILL_METADATA_PATH = ".agents/skills/truthmark-sync/agents/openai.yaml";
3125
+ var TRUTHMARK_REALIZE_SKILL_PATH = ".agents/skills/truthmark-realize/SKILL.md";
3126
+ var TRUTHMARK_REALIZE_SKILL_METADATA_PATH = ".agents/skills/truthmark-realize/agents/openai.yaml";
3127
+ var TRUTHMARK_CHECK_SKILL_PATH = ".agents/skills/truthmark-check/SKILL.md";
3128
+ var TRUTHMARK_CHECK_SKILL_METADATA_PATH = ".agents/skills/truthmark-check/agents/openai.yaml";
3129
+ var TRUTHMARK_PREVIEW_SKILL_PATH = ".agents/skills/truthmark-preview/SKILL.md";
3130
+ var TRUTHMARK_PREVIEW_SKILL_METADATA_PATH = ".agents/skills/truthmark-preview/agents/openai.yaml";
3131
+ var TRUTHMARK_PORTAL_SKILL_PATH = ".agents/skills/truthmark-portal/SKILL.md";
3132
+ var TRUTHMARK_PORTAL_SKILL_METADATA_PATH = ".agents/skills/truthmark-portal/agents/openai.yaml";
2936
3133
  var TRUTHMARK_ROUTE_AUDITOR_AGENT_PATH = ".codex/agents/truth-route-auditor.toml";
2937
3134
  var TRUTHMARK_CLAIM_VERIFIER_AGENT_PATH = ".codex/agents/truth-claim-verifier.toml";
2938
3135
  var TRUTHMARK_DOC_REVIEWER_AGENT_PATH = ".codex/agents/truth-doc-reviewer.toml";
@@ -2963,10 +3160,10 @@ var TRUTHMARK_COPILOT_REALIZE_PROMPT_PATH = ".github/prompts/truthmark-realize.p
2963
3160
  var TRUTHMARK_COPILOT_CHECK_PROMPT_PATH = ".github/prompts/truthmark-check.prompt.md";
2964
3161
  var TRUTHMARK_COPILOT_PREVIEW_PROMPT_PATH = ".github/prompts/truthmark-preview.prompt.md";
2965
3162
  var TRUTHMARK_COPILOT_PORTAL_PROMPT_PATH = ".github/prompts/truthmark-portal.prompt.md";
2966
- var TRUTHMARK_COPILOT_ROUTE_AUDITOR_AGENT_PATH = ".github/agents/truth-route-auditor.agent.md";
2967
- var TRUTHMARK_COPILOT_CLAIM_VERIFIER_AGENT_PATH = ".github/agents/truth-claim-verifier.agent.md";
2968
- var TRUTHMARK_COPILOT_DOC_REVIEWER_AGENT_PATH = ".github/agents/truth-doc-reviewer.agent.md";
2969
- var TRUTHMARK_COPILOT_DOC_WRITER_AGENT_PATH = ".github/agents/truth-doc-writer.agent.md";
3163
+ var TRUTHMARK_COPILOT_ROUTE_AUDITOR_AGENT_PATH = ".github/agents/truth-route-auditor.md";
3164
+ var TRUTHMARK_COPILOT_CLAIM_VERIFIER_AGENT_PATH = ".github/agents/truth-claim-verifier.md";
3165
+ var TRUTHMARK_COPILOT_DOC_REVIEWER_AGENT_PATH = ".github/agents/truth-doc-reviewer.md";
3166
+ var TRUTHMARK_COPILOT_DOC_WRITER_AGENT_PATH = ".github/agents/truth-doc-writer.md";
2970
3167
  var renderGeminiCommand = (description, prompt) => {
2971
3168
  const promptWithArgs = `${prompt.trimEnd()}
2972
3169
  User focus or arguments: {{args}}`;
@@ -2992,7 +3189,7 @@ var renderTomlStringArray = (values) => {
2992
3189
  return `[${values.map(renderTomlString).join(", ")}]`;
2993
3190
  };
2994
3191
  var TRUTH_REALIZE_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-realize; Codex /truthmark-realize or $truthmark-realize; Claude Code /truthmark-realize; GitHub Copilot /truthmark-realize; Gemini CLI /truthmark:realize.";
2995
- var routeFilesHint = (config) => `${config.docs.routing.rootIndex}; ${config.docs.routing.areaFilesRoot}/`;
3192
+ var routeFilesHint = (config) => `${config.truthmark.paths.routesIndex}; ${config.truthmark.paths.routeAreasRoot}/`;
2996
3193
  var WORKFLOW_PACKAGE_DEFINITIONS = {
2997
3194
  "truthmark-structure": {
2998
3195
  title: "Truthmark Structure",
@@ -3020,7 +3217,7 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3020
3217
  "Document current implemented behavior; do not invent future behavior.",
3021
3218
  "May write canonical truth docs and truth routing files only; must not write functional code.",
3022
3219
  "Read support/procedure.md before editing truth docs.",
3023
- "Read support/subagents-and-leases.md before dispatching or accepting worker output.",
3220
+ "Read support/subagents-and-leases.md only when dispatching or accepting worker output.",
3024
3221
  "Read support/report-template.md before the final report."
3025
3222
  ],
3026
3223
  parentRule: "Parent agent owns Truth Document acceptance, lease validation, and final report"
@@ -3037,7 +3234,7 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3037
3234
  "direct checkout inspection is the canonical path; do not require the truthmark binary.",
3038
3235
  "May write canonical truth docs and truth routing files only; must not rewrite functional code.",
3039
3236
  "Read support/procedure.md before editing truth docs.",
3040
- "Read support/subagents-and-leases.md before dispatching or accepting worker output.",
3237
+ "Read support/subagents-and-leases.md only when dispatching or accepting worker output.",
3041
3238
  "Read support/report-template.md before the final report."
3042
3239
  ],
3043
3240
  parentRule: "Parent agent owns Truth Sync acceptance, lease validation, and final report"
@@ -3080,16 +3277,15 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3080
3277
  "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
3081
3278
  `Inspect .truthmark/config.yml and configured route files (${routeFilesHint(config)}) only when they exist; then inspect canonical docs and relevant implementation directly.`,
3082
3279
  "Report issues and suggested fixes; do not silently rewrite unrelated files.",
3083
- "Direct checkout inspection is valid even when local tooling is unavailable.",
3084
3280
  "Read support/procedure.md before auditing details.",
3085
- "Read support/subagents-and-leases.md before dispatching verifier subagents.",
3281
+ "Read support/subagents-and-leases.md only when dispatching verifier subagents.",
3086
3282
  "Read support/report-template.md before the final report."
3087
3283
  ],
3088
3284
  parentRule: "Parent agent owns the final Truth Check report"
3089
3285
  },
3090
3286
  "truthmark-portal": {
3091
3287
  title: "Truthmark Portal",
3092
- argumentHint: "Optional output path, template, or portal generation focus",
3288
+ argumentHint: "Optional portal generation focus",
3093
3289
  invocations: TRUTHMARK_PORTAL_EXPLICIT_INVOCATIONS,
3094
3290
  use: () => "Use this skill only when the user explicitly asks to generate or refresh the committed static HTML Truthmark Portal.",
3095
3291
  quickRules: (config) => [
@@ -3098,8 +3294,8 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3098
3294
  "Markdown remains canonical; generated HTML is non-canonical presentation only.",
3099
3295
  "Read Markdown directly; the workflow does not require the truthmark CLI or package.",
3100
3296
  "Generate committed, generated non-canonical static files for humans.",
3101
- `Write only under configured Portal output ${config.truthmarkPortal.output}; default output is docs/truthmark-portal.`,
3102
- `Use configured Portal template ${config.truthmarkPortal.template}; no .truthmark/index.json dependency.`,
3297
+ `Write only under fixed Portal output ${config.truthmark.paths.portalOutput}.`,
3298
+ `Use determined Portal template ${config.truthmark.paths.portalTemplate} when present; no .truthmark/index.json dependency.`,
3103
3299
  "Use no remote dependencies by default and include source provenance on every page.",
3104
3300
  "Read support/procedure.md before generating Portal output.",
3105
3301
  "Read support/report-template.md before the final report."
@@ -3203,7 +3399,25 @@ var renderStandaloneWorkflowSkillBody = (workflowId, config) => {
3203
3399
  var renderWorkflowEntrypoint = (workflowId, config, supportFiles, host) => {
3204
3400
  const workflow = getTruthmarkWorkflow(workflowId);
3205
3401
  const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
3206
- const supportFileList = supportFiles.map((supportFile) => `- ${supportFile}`).join("\n");
3402
+ const supportFileUsage = (supportFile) => {
3403
+ if (supportFile === "support/procedure.md") {
3404
+ return "read before edits or detailed auditing; contains core quality gates";
3405
+ }
3406
+ if (supportFile === "support/report-template.md") {
3407
+ return "read before the final report";
3408
+ }
3409
+ if (supportFile === "support/subagents-and-leases.md") {
3410
+ return "read only when using subagents, leases, or accepting worker output";
3411
+ }
3412
+ if (supportFile === "support/helper-policy.md") {
3413
+ return "read only when invoking helper validators or reporting helper status";
3414
+ }
3415
+ if (supportFile === "helper-manifest.yml") {
3416
+ return "read only when invoking helper validators or validating helper registration";
3417
+ }
3418
+ return "available when relevant to the current step";
3419
+ };
3420
+ const supportFileList = supportFiles.map((supportFile) => `- ${supportFile} \u2014 ${supportFileUsage(supportFile)}`).join("\n");
3207
3421
  const hostUsage = host === "github-copilot" ? "Use as a Copilot agent skill. Prompt files remain available under `.github/prompts/` for command-style invocation in supported Copilot IDEs." : host === "gemini-cli" ? "Use as a Gemini CLI Agent Skill; commands remain available under `/truthmark:*` for command-first invocation." : void 0;
3208
3422
  return `---
3209
3423
  name: ${workflowId}
@@ -3215,10 +3429,9 @@ truthmark-version: ${TRUTHMARK_VERSION}
3215
3429
 
3216
3430
  # ${definition.title}
3217
3431
 
3218
- ${definition.use(config)}
3219
- ${hostUsage === void 0 ? "" : `
3220
- ${hostUsage}
3221
- `}
3432
+ ${definition.use(config)}${hostUsage === void 0 ? "" : `
3433
+
3434
+ ${hostUsage}`}
3222
3435
 
3223
3436
  Invocations: ${definition.invocations}
3224
3437
 
@@ -3348,10 +3561,10 @@ var renderOpenCodeWriterEditAllowRules = (config) => {
3348
3561
  resolveTruthDocsRoot(config)
3349
3562
  );
3350
3563
  const rootRouteIndex = normalizeOpenCodePermissionPath(
3351
- config.docs.routing.rootIndex
3564
+ config.truthmark.paths.routesIndex
3352
3565
  );
3353
3566
  const areaFilesRoot = normalizeOpenCodePermissionPath(
3354
- config.docs.routing.areaFilesRoot
3567
+ config.truthmark.paths.routeAreasRoot
3355
3568
  );
3356
3569
  const allowedPatterns = [
3357
3570
  appendOpenCodePermissionGlob(truthDocsRoot, "/**"),
@@ -4121,7 +4334,7 @@ var codexFiles = (config) => {
4121
4334
  content: renderTruthmarkDocWriterAgent()
4122
4335
  }
4123
4336
  ];
4124
- if (config.truthmarkPortal.enabled) {
4337
+ if (config.truthmark.generated.portal.enabled) {
4125
4338
  files.push(
4126
4339
  ...renderTruthmarkSkillPackage({
4127
4340
  skillPath: TRUTHMARK_PORTAL_SKILL_PATH,
@@ -4192,7 +4405,7 @@ var opencodeFiles = (config) => {
4192
4405
  content: renderTruthmarkOpenCodeDocWriterAgent(config)
4193
4406
  }
4194
4407
  ];
4195
- if (config.truthmarkPortal.enabled) {
4408
+ if (config.truthmark.generated.portal.enabled) {
4196
4409
  files.push(
4197
4410
  ...renderTruthmarkSkillPackage({
4198
4411
  skillPath: ".opencode/skills/truthmark-portal/SKILL.md",
@@ -4260,7 +4473,7 @@ var claudeFiles = (config, block) => {
4260
4473
  content: renderTruthmarkClaudeDocWriterAgent()
4261
4474
  }
4262
4475
  ];
4263
- if (config.truthmarkPortal.enabled) {
4476
+ if (config.truthmark.generated.portal.enabled) {
4264
4477
  files.push(
4265
4478
  ...renderTruthmarkSkillPackage({
4266
4479
  skillPath: ".claude/skills/truthmark-portal/SKILL.md",
@@ -4352,7 +4565,7 @@ var copilotFiles = (config, block) => {
4352
4565
  content: renderTruthmarkCopilotDocWriterAgent()
4353
4566
  }
4354
4567
  ];
4355
- if (config.truthmarkPortal.enabled) {
4568
+ if (config.truthmark.generated.portal.enabled) {
4356
4569
  files.push(
4357
4570
  ...renderTruthmarkSkillPackage({
4358
4571
  skillPath: ".github/skills/truthmark-portal/SKILL.md",
@@ -4448,7 +4661,7 @@ var geminiFiles = (config, block) => {
4448
4661
  content: renderTruthmarkGeminiDocWriterAgent()
4449
4662
  }
4450
4663
  ];
4451
- if (config.truthmarkPortal.enabled) {
4664
+ if (config.truthmark.generated.portal.enabled) {
4452
4665
  files.push(
4453
4666
  ...renderTruthmarkSkillPackage({
4454
4667
  skillPath: ".gemini/skills/truthmark-portal/SKILL.md",
@@ -4502,19 +4715,10 @@ var escapeRegExp = (value) => {
4502
4715
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4503
4716
  };
4504
4717
  var MANAGED_WORKFLOW_HEADING = "## Truthmark Workflow";
4505
- var LEGACY_MANAGED_LINES = [
4506
- "### Truth Sync",
4507
- "- may read changed functional code files",
4508
- "- may write truth docs only",
4509
- "- must not rewrite functional code"
4510
- ];
4511
- var CANONICAL_MANAGED_LINES = /* @__PURE__ */ new Set(
4512
- [
4513
- ...renderAgentsBlock().split("\n").map((line) => line.trim()).filter(
4514
- (line) => line.length > 0 && line !== TRUTHMARK_BLOCK_START && line !== TRUTHMARK_BLOCK_END
4515
- ),
4516
- ...LEGACY_MANAGED_LINES
4517
- ]
4718
+ var CANONICAL_MANAGED_LINES = new Set(
4719
+ renderAgentsBlock().split("\n").map((line) => line.trim()).filter(
4720
+ (line) => line.length > 0 && line !== TRUTHMARK_BLOCK_START && line !== TRUTHMARK_BLOCK_END
4721
+ )
4518
4722
  );
4519
4723
  var countCanonicalManagedLineMatches = (lines) => {
4520
4724
  return lines.reduce((matchCount, line) => {
@@ -4541,46 +4745,11 @@ var removeTrailingManagedChunk = (preservedLines) => {
4541
4745
  preservedLines.splice(startIndex);
4542
4746
  }
4543
4747
  };
4544
- var LEGACY_REPO_RULES_PATH = ["docs", "ai", `repo-${"rules.md"}`].join("/");
4545
- var LEGACY_AGENT_ONBOARDING_PATH = ["docs", "ai", `agent-${"onboarding.md"}`].join(
4546
- "/"
4547
- );
4548
- var LEGACY_PRIMARY_REPO_INSTRUCTION_PHRASE = [
4549
- "primary repository",
4550
- "instruction source"
4551
- ].join(" ");
4552
- var normalizeLegacyInstructionPreamble = (content) => {
4553
- return content.replaceAll(
4554
- `Follow \`${LEGACY_REPO_RULES_PATH}\`.`,
4555
- "Follow repository instruction files that are present in this checkout; do not assume optional policy docs exist."
4556
- ).replaceAll(
4557
- `Follow \`${LEGACY_REPO_RULES_PATH}\` as the ${LEGACY_PRIMARY_REPO_INSTRUCTION_PHRASE}.`,
4558
- "Follow repository instruction files that are present in this checkout; do not assume optional policy docs exist."
4559
- ).replaceAll(
4560
- `Use that file as the ${LEGACY_PRIMARY_REPO_INSTRUCTION_PHRASE} for Codex.`,
4561
- "Use explicitly configured repository policy docs only when they exist in this checkout."
4562
- ).replaceAll(
4563
- `Use that file as the ${LEGACY_PRIMARY_REPO_INSTRUCTION_PHRASE} for this agent.`,
4564
- "Use explicitly configured repository policy docs only when they exist in this checkout."
4565
- ).replaceAll("Codex-specific:", "Agent-specific:").replaceAll(
4566
- "- Read `docs/README.md` for the canonical docs map.",
4567
- "- Read the configured Truthmark routing files when choosing or updating canonical docs."
4568
- ).replaceAll(
4569
- "- Read `docs/README.md` only when choosing or updating canonical docs.",
4570
- "- Read the configured Truthmark routing files when choosing or updating canonical docs."
4571
- ).replaceAll(
4572
- `- Use \`${LEGACY_AGENT_ONBOARDING_PATH}\` for quick task routing.`,
4573
- "- Use repository onboarding or docs-map files only when present and needed for unclear or cross-area routing."
4574
- ).replaceAll(
4575
- `- Use \`${LEGACY_AGENT_ONBOARDING_PATH}\` only when task routing is unclear or cross-area.`,
4576
- "- Use repository onboarding or docs-map files only when present and needed for unclear or cross-area routing."
4577
- );
4578
- };
4579
4748
  var upsertManagedBlock = (existingContent, block) => {
4580
4749
  if (!existingContent || existingContent.trim().length === 0) {
4581
4750
  return block;
4582
4751
  }
4583
- const normalizedExistingContent = normalizeLegacyInstructionPreamble(existingContent);
4752
+ const normalizedExistingContent = existingContent;
4584
4753
  const startMarkerPattern = new RegExp(escapeRegExp(TRUTHMARK_BLOCK_START), "g");
4585
4754
  const endMarkerPattern = new RegExp(escapeRegExp(TRUTHMARK_BLOCK_END), "g");
4586
4755
  const managedBlockPattern = new RegExp(
@@ -4649,13 +4818,13 @@ var diagnosticCategoryForPath = (filePath, config) => {
4649
4818
  if (filePath === "AGENTS.md") {
4650
4819
  return "truth-sync";
4651
4820
  }
4652
- if (filePath === ".github/prompts/truthmark-realize.prompt.md" || filePath.startsWith(".github/skills/truthmark-realize/") || filePath.startsWith(".claude/skills/truthmark-realize/") || filePath.startsWith(".opencode/skills/truthmark-realize/") || filePath.startsWith(".codex/skills/truthmark-realize/") || filePath.startsWith(".gemini/skills/truthmark-realize/")) {
4821
+ if (filePath === ".github/prompts/truthmark-realize.prompt.md" || filePath.startsWith(".github/skills/truthmark-realize/") || filePath.startsWith(".claude/skills/truthmark-realize/") || filePath.startsWith(".opencode/skills/truthmark-realize/") || filePath.startsWith(".agents/skills/truthmark-realize/") || filePath.startsWith(".gemini/skills/truthmark-realize/")) {
4653
4822
  return "realization";
4654
4823
  }
4655
4824
  if (filePath === "CLAUDE.md" || filePath === "GEMINI.md" || filePath === ".github/copilot-instructions.md" || filePath.startsWith(".github/prompts/truthmark-") || filePath.startsWith(".github/agents/truth-") || filePath.startsWith(".github/skills/truthmark-") || filePath.startsWith(".claude/agents/truth-") || filePath.startsWith(".claude/skills/truthmark-") || filePath.startsWith(".opencode/skills/truthmark-") || filePath.startsWith(".opencode/agents/") || filePath.startsWith(".codex/agents/") || filePath.startsWith(".gemini/agents/truth-") || filePath.startsWith(".gemini/skills/truthmark-")) {
4656
4825
  return "truth-sync";
4657
4826
  }
4658
- if (filePath.startsWith(".codex/skills/truthmark-")) {
4827
+ if (filePath.startsWith(".agents/skills/truthmark-")) {
4659
4828
  return "truth-sync";
4660
4829
  }
4661
4830
  if (filePath.startsWith(".gemini/commands/truthmark/realize")) {
@@ -4664,8 +4833,8 @@ var diagnosticCategoryForPath = (filePath, config) => {
4664
4833
  if (filePath.startsWith(".gemini/commands/truthmark/")) {
4665
4834
  return "truth-sync";
4666
4835
  }
4667
- if (filePath === config.docs.routing.rootIndex) {
4668
- return "authority";
4836
+ if (filePath === config.truthmark.paths.routesIndex) {
4837
+ return "area-index";
4669
4838
  }
4670
4839
  return "config";
4671
4840
  };
@@ -4700,7 +4869,7 @@ var runInit = async (cwd) => {
4700
4869
  if (!loadedConfig.config) {
4701
4870
  return {
4702
4871
  command: "init",
4703
- summary: "Truthmark init requires .truthmark/config.yml. Run truthmark config first, review the hierarchy, then run truthmark init.",
4872
+ summary: "Truthmark init requires .truthmark/config.yml. Run truthmark config first, review the workspace paths, then run truthmark init.",
4704
4873
  diagnostics: loadedConfig.diagnostics,
4705
4874
  data: {
4706
4875
  repositoryRoot: repository.repositoryRoot,
@@ -4711,14 +4880,9 @@ var runInit = async (cwd) => {
4711
4880
  }
4712
4881
  };
4713
4882
  }
4714
- const defaultStandards = renderDefaultStandards([]);
4715
4883
  const results = [];
4716
- for (const template of defaultStandards) {
4717
- results.push(await ensureRepoFile(rootDir, template.path, template.content));
4718
- }
4719
4884
  const config = loadedConfig.config;
4720
4885
  results.push(...await scaffoldHierarchy(rootDir, config));
4721
- const migrationDiagnostics = await detectHierarchyMigrationDiagnostics(rootDir, config);
4722
4886
  const block = renderAgentsBlock(config);
4723
4887
  const platformFiles = renderGeneratedSurfaces(config, block);
4724
4888
  for (const file of platformFiles) {
@@ -4728,7 +4892,7 @@ var runInit = async (cwd) => {
4728
4892
  return {
4729
4893
  command: "init",
4730
4894
  summary: changedResults.length > 0 ? "Initialized or updated the Truthmark repository scaffold." : "Truthmark repository scaffold is already up to date.",
4731
- diagnostics: [...writeDiagnostics(results, config), ...migrationDiagnostics],
4895
+ diagnostics: writeDiagnostics(results, config),
4732
4896
  data: {
4733
4897
  repositoryRoot: repository.repositoryRoot,
4734
4898
  worktreePath: repository.worktreePath,
@@ -4741,7 +4905,7 @@ var runInit = async (cwd) => {
4741
4905
 
4742
4906
  // src/checks/branch-scope.ts
4743
4907
  import fs8 from "fs/promises";
4744
- import fg2 from "fast-glob";
4908
+ import fg from "fast-glob";
4745
4909
 
4746
4910
  // src/markdown/hash.ts
4747
4911
  import { createHash } from "crypto";
@@ -4782,10 +4946,11 @@ var getBranchScopeData = async (cwd) => {
4782
4946
  const repository = await getGitRepository(cwd);
4783
4947
  const relevantFileHashes = {};
4784
4948
  const loadResult = await loadConfig(repository.worktreePath);
4785
- const rootIndex = loadResult.config?.docs.routing.rootIndex ?? DEFAULT_DOCS_HIERARCHY.routing.root_index;
4786
- const areaFilesRoot = loadResult.config?.docs.routing.areaFilesRoot ?? DEFAULT_DOCS_HIERARCHY.routing.area_files_root;
4949
+ const defaultConfig = createDefaultConfig();
4950
+ const rootIndex = loadResult.config?.truthmark.paths.routesIndex ?? defaultConfig.truthmark.paths.routesIndex;
4951
+ const areaFilesRoot = loadResult.config?.truthmark.paths.routeAreasRoot ?? defaultConfig.truthmark.paths.routeAreasRoot;
4787
4952
  const relevantFiles = /* @__PURE__ */ new Set([...RELEVANT_BRANCH_SCOPE_FILES, rootIndex]);
4788
- const routeFiles = await fg2([`${areaFilesRoot}/**/*.md`], {
4953
+ const routeFiles = await fg([`${areaFilesRoot}/**/*.md`], {
4789
4954
  cwd: repository.worktreePath,
4790
4955
  onlyFiles: true,
4791
4956
  followSymbolicLinks: false
@@ -4813,7 +4978,7 @@ var getBranchScopeData = async (cwd) => {
4813
4978
 
4814
4979
  // src/checks/authority.ts
4815
4980
  import fs9 from "fs/promises";
4816
- import fg3 from "fast-glob";
4981
+ import fg2 from "fast-glob";
4817
4982
  var looksLikeGlob = (pattern) => {
4818
4983
  return /[*?[\]{}()!+@]/u.test(pattern);
4819
4984
  };
@@ -4828,11 +4993,11 @@ var pathExists = async (absolutePath) => {
4828
4993
  throw error;
4829
4994
  }
4830
4995
  };
4831
- var checkAuthority = async (rootDir, config) => {
4996
+ var checkControlledPaths = async (rootDir, controlledPaths) => {
4832
4997
  const diagnostics = [];
4833
4998
  const orderedPaths = [];
4834
4999
  const seenPaths = /* @__PURE__ */ new Set();
4835
- for (const entry of config.authority) {
5000
+ for (const entry of controlledPaths) {
4836
5001
  if (looksLikeGlob(entry)) {
4837
5002
  try {
4838
5003
  resolveRepoPath(rootDir, entry);
@@ -4840,17 +5005,17 @@ var checkAuthority = async (rootDir, config) => {
4840
5005
  diagnostics.push({
4841
5006
  category: "authority",
4842
5007
  severity: "error",
4843
- message: `Authority entry ${entry} must stay inside the repository root.`,
5008
+ message: `Truthmark-controlled path ${entry} must stay inside the repository root.`,
4844
5009
  file: entry
4845
5010
  });
4846
5011
  continue;
4847
5012
  }
4848
- const matches = (await fg3([entry], { cwd: rootDir, onlyFiles: true })).sort();
5013
+ const matches = (await fg2([entry], { cwd: rootDir, onlyFiles: true })).sort();
4849
5014
  if (matches.length === 0) {
4850
5015
  diagnostics.push({
4851
5016
  category: "authority",
4852
5017
  severity: "review",
4853
- message: `Authority glob ${entry} did not match any files.`,
5018
+ message: `Truthmark-controlled glob ${entry} did not match any files.`,
4854
5019
  file: entry
4855
5020
  });
4856
5021
  }
@@ -4862,7 +5027,7 @@ var checkAuthority = async (rootDir, config) => {
4862
5027
  diagnostics.push({
4863
5028
  category: "authority",
4864
5029
  severity: "error",
4865
- message: `Authority path ${match} must stay inside the repository root.`,
5030
+ message: `Truthmark-controlled path ${match} must stay inside the repository root.`,
4866
5031
  file: match
4867
5032
  });
4868
5033
  continue;
@@ -4882,7 +5047,7 @@ var checkAuthority = async (rootDir, config) => {
4882
5047
  diagnostics.push({
4883
5048
  category: "authority",
4884
5049
  severity: "error",
4885
- message: `Authority entry ${entry} must stay inside the repository root.`,
5050
+ message: `Truthmark-controlled path ${entry} must stay inside the repository root.`,
4886
5051
  file: entry
4887
5052
  });
4888
5053
  continue;
@@ -4891,7 +5056,7 @@ var checkAuthority = async (rootDir, config) => {
4891
5056
  diagnostics.push({
4892
5057
  category: "authority",
4893
5058
  severity: "error",
4894
- message: `Missing authority file ${entry}.`,
5059
+ message: `Missing Truthmark-controlled file ${entry}.`,
4895
5060
  file: entry
4896
5061
  });
4897
5062
  continue;
@@ -4906,6 +5071,9 @@ var checkAuthority = async (rootDir, config) => {
4906
5071
  diagnostics
4907
5072
  };
4908
5073
  };
5074
+ var checkAuthority = async (rootDir, config) => {
5075
+ return checkControlledPaths(rootDir, config.truthmark.controlledPaths);
5076
+ };
4909
5077
 
4910
5078
  // src/checks/frontmatter.ts
4911
5079
  import fs10 from "fs/promises";
@@ -5082,12 +5250,12 @@ var checkLinks = async (rootDir, markdownPaths) => {
5082
5250
 
5083
5251
  // src/checks/areas.ts
5084
5252
  import fs13 from "fs/promises";
5085
- import fg5 from "fast-glob";
5253
+ import fg4 from "fast-glob";
5086
5254
  import micromatch4 from "micromatch";
5087
5255
 
5088
5256
  // src/routing/area-resolver.ts
5089
5257
  import fs12 from "fs/promises";
5090
- import fg4 from "fast-glob";
5258
+ import fg3 from "fast-glob";
5091
5259
  import micromatch2 from "micromatch";
5092
5260
  var unique = (values) => {
5093
5261
  return [...new Set(values)];
@@ -5252,7 +5420,7 @@ var resolveAreaRouting = async (rootDir, config) => {
5252
5420
  );
5253
5421
  }
5254
5422
  }
5255
- const routeFilesUnderRoot = await fg4([`${config.areaFilesRoot}/**/*.md`], {
5423
+ const routeFilesUnderRoot = await fg3([`${config.areaFilesRoot}/**/*.md`], {
5256
5424
  cwd: rootDir,
5257
5425
  onlyFiles: true,
5258
5426
  followSymbolicLinks: false
@@ -5414,7 +5582,7 @@ var classifyPath = (filePath, ignorePatterns) => {
5414
5582
  if (normalizedPath.startsWith(".truthmark/")) {
5415
5583
  return "derived";
5416
5584
  }
5417
- if (normalizedPath.startsWith(".claude/") || normalizedPath.startsWith(".codex/") || normalizedPath.startsWith(".gemini/commands/") || normalizedPath.startsWith(".opencode/") || normalizedPath === ".github/copilot-instructions.md" || normalizedPath.startsWith(".github/agents/truth-") || normalizedPath.startsWith(".github/prompts/truthmark-") || normalizedPath === "AGENTS.md" || normalizedPath === "CLAUDE.md" || normalizedPath === "GEMINI.md" || normalizedPath.startsWith(".gemini/commands/truthmark/")) {
5585
+ if (normalizedPath.startsWith(".agents/skills/truthmark-") || normalizedPath.startsWith(".claude/") || normalizedPath.startsWith(".codex/") || normalizedPath.startsWith(".gemini/agents/truth-") || normalizedPath.startsWith(".gemini/commands/") || normalizedPath.startsWith(".gemini/skills/truthmark-") || normalizedPath.startsWith(".opencode/") || normalizedPath === ".github/copilot-instructions.md" || normalizedPath.startsWith(".github/agents/truth-") || normalizedPath.startsWith(".github/prompts/truthmark-") || normalizedPath.startsWith(".github/skills/truthmark-") || normalizedPath === "AGENTS.md" || normalizedPath === "CLAUDE.md" || normalizedPath === "GEMINI.md" || normalizedPath.startsWith(".gemini/commands/truthmark/")) {
5418
5586
  return "derived";
5419
5587
  }
5420
5588
  if (ignorePatterns.length > 0 && micromatch3.isMatch(normalizedPath, ignorePatterns)) {
@@ -5490,11 +5658,11 @@ var isBroadCodeSurface = (pattern) => {
5490
5658
  };
5491
5659
  var checkAreas = async (rootDir, config) => {
5492
5660
  const routing = await resolveAreaRouting(rootDir, {
5493
- rootIndex: config.docs.routing.rootIndex,
5494
- areaFilesRoot: config.docs.routing.areaFilesRoot,
5661
+ rootIndex: config.truthmark.paths.routesIndex,
5662
+ areaFilesRoot: config.truthmark.paths.routeAreasRoot,
5495
5663
  truthDocsRoot: resolveTruthDocsRoot(config)
5496
5664
  });
5497
- const discoveredCodeFiles = await fg5([...COVERAGE_SCAN_PATTERNS], {
5665
+ const discoveredCodeFiles = await fg4([...COVERAGE_SCAN_PATTERNS], {
5498
5666
  cwd: rootDir,
5499
5667
  onlyFiles: true,
5500
5668
  ignore: config.ignore,
@@ -5547,7 +5715,7 @@ var checkAreas = async (rootDir, config) => {
5547
5715
  const routedGlobEntry = area.truthDocumentEntries.find(
5548
5716
  (entry) => entry.path === truthDocument
5549
5717
  );
5550
- const matches = (await fg5([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
5718
+ const matches = (await fg4([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
5551
5719
  if (matches.length === 0) {
5552
5720
  diagnostics.push({
5553
5721
  category: "area-index",
@@ -5650,7 +5818,7 @@ var checkAreas = async (rootDir, config) => {
5650
5818
  entry.valid = false;
5651
5819
  continue;
5652
5820
  }
5653
- const matches = await fg5([codeSurfaceEntry], {
5821
+ const matches = await fg4([codeSurfaceEntry], {
5654
5822
  cwd: rootDir,
5655
5823
  onlyFiles: true,
5656
5824
  followSymbolicLinks: false
@@ -5801,11 +5969,7 @@ var kindSpecificHeadingMessages = (source, kind) => {
5801
5969
  return [];
5802
5970
  };
5803
5971
  var decisionTruthGlobs = (config) => {
5804
- return [
5805
- config.docs.roots.architecture,
5806
- resolveTruthDocsRoot(config),
5807
- config.docs.roots.api
5808
- ].filter((root) => Boolean(root)).map((root) => `${root}/**/*.md`);
5972
+ return [`${resolveTruthDocsRoot(config)}/**/*.md`];
5809
5973
  };
5810
5974
  var isDecisionTruthCandidate = (config, filePath) => {
5811
5975
  return !filePath.endsWith("/README.md") && micromatch5.isMatch(filePath, decisionTruthGlobs(config));
@@ -5937,7 +6101,7 @@ import path8 from "path";
5937
6101
  import fs16 from "fs/promises";
5938
6102
  import path6 from "path";
5939
6103
  import { execa as execa2 } from "execa";
5940
- import fg6 from "fast-glob";
6104
+ import fg5 from "fast-glob";
5941
6105
  import matter2 from "gray-matter";
5942
6106
  import micromatch6 from "micromatch";
5943
6107
  var languageByExtension = /* @__PURE__ */ new Map([
@@ -6019,7 +6183,7 @@ var isIgnoredPath = (filePath, ignore) => {
6019
6183
  return micromatch6.isMatch(filePath, [...defaultIgnore, ...ignore]);
6020
6184
  };
6021
6185
  var discoverRepoFiles = async (rootDir, ignore) => {
6022
- const discoveredFiles = await gitDiscoverableFiles(rootDir) ?? await fg6(["**/*"], {
6186
+ const discoveredFiles = await gitDiscoverableFiles(rootDir) ?? await fg5(["**/*"], {
6023
6187
  cwd: rootDir,
6024
6188
  onlyFiles: true,
6025
6189
  dot: true,
@@ -6080,7 +6244,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6080
6244
  // src/repo-index/package-metadata.ts
6081
6245
  import fs17 from "fs/promises";
6082
6246
  import path7 from "path";
6083
- import fg7 from "fast-glob";
6247
+ import fg6 from "fast-glob";
6084
6248
  var packageManagerFor = async (rootDir, packageDir) => {
6085
6249
  const lockfiles = [
6086
6250
  ["package-lock.json", "npm"],
@@ -6100,7 +6264,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
6100
6264
  return "npm";
6101
6265
  };
6102
6266
  var discoverPackageMetadata = async (rootDir) => {
6103
- const packageFiles = await fg7(["package.json", "*/package.json", "packages/*/package.json"], {
6267
+ const packageFiles = await fg6(["package.json", "*/package.json", "packages/*/package.json"], {
6104
6268
  cwd: rootDir,
6105
6269
  onlyFiles: true,
6106
6270
  ignore: ["node_modules/**", "dist/**", "build/**"],
@@ -6134,8 +6298,8 @@ var buildRouteMap = async (rootDir) => {
6134
6298
  };
6135
6299
  }
6136
6300
  const routing = await resolveAreaRouting(rootDir, {
6137
- rootIndex: loadResult.config.docs.routing.rootIndex,
6138
- areaFilesRoot: loadResult.config.docs.routing.areaFilesRoot,
6301
+ rootIndex: loadResult.config.truthmark.paths.routesIndex,
6302
+ areaFilesRoot: loadResult.config.truthmark.paths.routeAreasRoot,
6139
6303
  truthDocsRoot: resolveTruthDocsRoot(loadResult.config)
6140
6304
  });
6141
6305
  return {
@@ -6640,16 +6804,37 @@ var buildImpactSet = async (cwd, options) => {
6640
6804
  };
6641
6805
  };
6642
6806
 
6807
+ // src/freshness/check.ts
6808
+ var checkFreshness = async (rootDir, _config, _truthDocumentPaths, base) => {
6809
+ const impactSet = await buildImpactSet(rootDir, { base });
6810
+ const diagnostics = [];
6811
+ for (const diagnostic of impactSet.diagnostics) {
6812
+ if (diagnostic.category !== "impact") {
6813
+ continue;
6814
+ }
6815
+ diagnostics.push({
6816
+ ...diagnostic,
6817
+ category: "freshness",
6818
+ message: diagnostic.message.replace("not mapped to a Truthmark route", "not routed to truth ownership")
6819
+ });
6820
+ }
6821
+ return {
6822
+ diagnostics,
6823
+ impactSet
6824
+ };
6825
+ };
6826
+
6643
6827
  // src/evidence/validate.ts
6644
6828
  import fs21 from "fs/promises";
6645
- import fg8 from "fast-glob";
6829
+ import fg7 from "fast-glob";
6646
6830
 
6647
6831
  // src/evidence/parse.ts
6648
6832
  import fs20 from "fs/promises";
6649
6833
  import path11 from "path";
6650
6834
  import matter3 from "gray-matter";
6651
6835
  import { parse as parse3 } from "yaml";
6652
- var evidenceBlockPattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
6836
+ var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
6837
+ var topLevelEvidenceMarkerPattern = /^evidence\s*:/imu;
6653
6838
  var repoRootPrefixes = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
6654
6839
  var normalizeReferencePath = (truthDocPath, referencePath) => {
6655
6840
  const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
@@ -6688,8 +6873,12 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
6688
6873
  source: "frontmatter"
6689
6874
  });
6690
6875
  }
6691
- for (const match of parsed.content.matchAll(evidenceBlockPattern)) {
6692
- const block = parse3(match[1] ?? "");
6876
+ for (const match of parsed.content.matchAll(yamlFencePattern)) {
6877
+ const yamlBlock = match[1] ?? "";
6878
+ if (!topLevelEvidenceMarkerPattern.test(yamlBlock)) {
6879
+ continue;
6880
+ }
6881
+ const block = parse3(yamlBlock);
6693
6882
  const rawEvidence = block && typeof block === "object" && "evidence" in block ? block.evidence : null;
6694
6883
  if (!Array.isArray(rawEvidence)) {
6695
6884
  continue;
@@ -6714,7 +6903,7 @@ var pathExists5 = async (filePath) => {
6714
6903
  }
6715
6904
  };
6716
6905
  var diagnosticFor = (reference, message) => ({
6717
- category: "freshness",
6906
+ category: "source-traceability",
6718
6907
  severity: "error",
6719
6908
  message,
6720
6909
  file: reference.truthDocPath,
@@ -6723,12 +6912,21 @@ var diagnosticFor = (reference, message) => ({
6723
6912
  source: reference.source
6724
6913
  }
6725
6914
  });
6915
+ var parseDiagnosticFor = (truthDocPath, error) => ({
6916
+ category: "source-traceability",
6917
+ severity: "error",
6918
+ message: `Malformed evidence YAML block in ${truthDocPath}: ${error instanceof Error ? error.message : String(error)}`,
6919
+ file: truthDocPath,
6920
+ data: {
6921
+ source: "evidence-block"
6922
+ }
6923
+ });
6726
6924
  var isGlobReference = (referencePath) => /[*?[\]{}()]/u.test(referencePath);
6727
6925
  var validateGlob = async (rootDir, reference) => {
6728
6926
  if (reference.path.startsWith("../") || reference.path.startsWith("/")) {
6729
6927
  return diagnosticFor(reference, `Referenced file pattern ${reference.path} must stay inside the repository root.`);
6730
6928
  }
6731
- const matches = await fg8(reference.path, {
6929
+ const matches = await fg7(reference.path, {
6732
6930
  cwd: rootDir,
6733
6931
  dot: true,
6734
6932
  onlyFiles: true,
@@ -6806,7 +7004,13 @@ var validateReference = async (rootDir, reference) => {
6806
7004
  var validateEvidenceReferences = async (rootDir, truthDocPaths) => {
6807
7005
  const diagnostics = [];
6808
7006
  for (const truthDocPath of [...truthDocPaths].sort()) {
6809
- const references = await parseEvidenceReferences(rootDir, truthDocPath);
7007
+ let references;
7008
+ try {
7009
+ references = await parseEvidenceReferences(rootDir, truthDocPath);
7010
+ } catch (error) {
7011
+ diagnostics.push(parseDiagnosticFor(truthDocPath, error));
7012
+ continue;
7013
+ }
6810
7014
  for (const reference of references) {
6811
7015
  diagnostics.push(...await validateReference(rootDir, reference));
6812
7016
  }
@@ -6814,25 +7018,135 @@ var validateEvidenceReferences = async (rootDir, truthDocPaths) => {
6814
7018
  return diagnostics;
6815
7019
  };
6816
7020
 
6817
- // src/freshness/check.ts
6818
- var checkFreshness = async (rootDir, _config, truthDocumentPaths, base) => {
6819
- const impactSet = await buildImpactSet(rootDir, { base });
6820
- const diagnostics = [...await validateEvidenceReferences(rootDir, truthDocumentPaths)];
6821
- for (const diagnostic of impactSet.diagnostics) {
6822
- if (diagnostic.category !== "impact") {
6823
- continue;
7021
+ // src/checks/scorecard.ts
7022
+ var TRUTH_HEALTH_SCORECARD_SCHEMA_VERSION = "truthmark-scorecard/v0";
7023
+ var TRUTH_HEALTH_DIMENSION_IDS = [
7024
+ "routing-coverage",
7025
+ "ownership-clarity",
7026
+ "source-traceability",
7027
+ "branch-freshness",
7028
+ "generated-surface-freshness",
7029
+ "truth-doc-structure",
7030
+ "decision-rationale-preservation"
7031
+ ];
7032
+ var EVIDENCE_LIMIT = 2;
7033
+ var textIncludesAny = (text, needles) => {
7034
+ const normalized = text.toLowerCase();
7035
+ return needles.some((needle) => normalized.includes(needle));
7036
+ };
7037
+ var diagnosticText = (diagnostic) => {
7038
+ const dataText = diagnostic.data ? JSON.stringify(diagnostic.data) : "";
7039
+ return `${diagnostic.message} ${diagnostic.file ?? ""} ${diagnostic.area ?? ""} ${dataText}`;
7040
+ };
7041
+ var isRouteAmbiguityDiagnostic = (diagnostic) => diagnostic.category === "context-pack" && textIncludesAny(diagnosticText(diagnostic), [
7042
+ "route",
7043
+ "routing",
7044
+ "ownership",
7045
+ "write boundary",
7046
+ "allowed write"
7047
+ ]);
7048
+ var isSourceTraceabilityDiagnostic = (diagnostic) => diagnostic.category === "source-traceability" || diagnostic.category === "links" && textIncludesAny(diagnosticText(diagnostic), [
7049
+ "source_of_truth",
7050
+ "source of truth",
7051
+ "source evidence",
7052
+ "evidence"
7053
+ ]);
7054
+ var isMarkdownShapeDiagnostic = (diagnostic) => diagnostic.category === "authority" && textIncludesAny(diagnosticText(diagnostic), ["markdown", "heading", "section"]);
7055
+ var isOwnershipFreshnessDiagnostic = (diagnostic) => diagnostic.category === "freshness" && textIncludesAny(diagnosticText(diagnostic), [
7056
+ "route",
7057
+ "routing",
7058
+ "ownership",
7059
+ "truth owner",
7060
+ "truth ownership",
7061
+ "affected truth document"
7062
+ ]);
7063
+ var isDecisionRationaleDiagnostic = (diagnostic) => diagnostic.category === "doc-structure" && textIncludesAny(diagnosticText(diagnostic), ["product decisions", "rationale"]);
7064
+ var mapsToDimension = (diagnostic, dimensionId) => {
7065
+ switch (dimensionId) {
7066
+ case "routing-coverage":
7067
+ return ["config", "authority", "area-index", "coverage", "repo-index"].includes(
7068
+ diagnostic.category
7069
+ );
7070
+ case "ownership-clarity":
7071
+ return ["config", "area-index", "coverage", "impact"].includes(diagnostic.category) || isRouteAmbiguityDiagnostic(diagnostic) || isOwnershipFreshnessDiagnostic(diagnostic);
7072
+ case "source-traceability":
7073
+ return isSourceTraceabilityDiagnostic(diagnostic);
7074
+ case "branch-freshness":
7075
+ return diagnostic.category === "freshness";
7076
+ case "generated-surface-freshness":
7077
+ return diagnostic.category === "config" || diagnostic.category === "generated-surface";
7078
+ case "truth-doc-structure":
7079
+ return diagnostic.category === "config" || diagnostic.category === "frontmatter" || diagnostic.category === "links" || diagnostic.category === "doc-structure" || isMarkdownShapeDiagnostic(diagnostic);
7080
+ case "decision-rationale-preservation":
7081
+ return isDecisionRationaleDiagnostic(diagnostic);
7082
+ }
7083
+ };
7084
+ var checkerRanForDimension = (dimensionId, context) => {
7085
+ switch (dimensionId) {
7086
+ case "routing-coverage":
7087
+ return context.routingChecksRan ?? true;
7088
+ case "ownership-clarity":
7089
+ return context.ownershipChecksRan ?? true;
7090
+ case "source-traceability":
7091
+ return context.evidenceChecksRan ?? true;
7092
+ case "branch-freshness":
7093
+ return context.branchFreshnessRan;
7094
+ case "generated-surface-freshness":
7095
+ return context.generatedSurfaceChecksRan ?? true;
7096
+ case "truth-doc-structure":
7097
+ return context.truthDocStructureChecksRan ?? true;
7098
+ case "decision-rationale-preservation":
7099
+ return context.decisionRationaleChecksRan ?? true;
7100
+ }
7101
+ };
7102
+ var statusForDiagnostics = (diagnostics, diagnosticIndexes, checkerRan) => {
7103
+ if (diagnosticIndexes.length > 0) {
7104
+ return diagnosticIndexes.some((index) => diagnostics[index]?.severity === "error") ? "fail" : "warn";
7105
+ }
7106
+ return checkerRan ? "pass" : "not-run";
7107
+ };
7108
+ var evidenceForDiagnostics = (diagnostics, diagnosticIndexes, status, dimensionId, context) => {
7109
+ if (status === "pass") {
7110
+ return void 0;
7111
+ }
7112
+ if (diagnosticIndexes.length === 0) {
7113
+ if (dimensionId === "branch-freshness") {
7114
+ return [context.branchFreshnessNotRunReason ?? "base not supplied"];
6824
7115
  }
6825
- diagnostics.push({
6826
- ...diagnostic,
6827
- category: "freshness",
6828
- message: diagnostic.message.replace("not mapped to a Truthmark route", "not routed to truth ownership")
6829
- });
7116
+ return ["checker not run"];
6830
7117
  }
6831
- return {
6832
- diagnostics,
6833
- impactSet
6834
- };
7118
+ return diagnosticIndexes.slice(0, EVIDENCE_LIMIT).map((index) => {
7119
+ const diagnostic = diagnostics[index];
7120
+ const subject = diagnostic.file ?? diagnostic.area;
7121
+ return subject ? `${diagnostic.category}:${subject}` : diagnostic.category;
7122
+ });
6835
7123
  };
7124
+ var buildTruthHealthScorecard = (diagnostics, context) => ({
7125
+ schemaVersion: TRUTH_HEALTH_SCORECARD_SCHEMA_VERSION,
7126
+ dimensions: TRUTH_HEALTH_DIMENSION_IDS.map((dimensionId) => {
7127
+ const diagnosticIndexes = diagnostics.flatMap(
7128
+ (diagnostic, index) => mapsToDimension(diagnostic, dimensionId) ? [index] : []
7129
+ );
7130
+ const status = statusForDiagnostics(
7131
+ diagnostics,
7132
+ diagnosticIndexes,
7133
+ checkerRanForDimension(dimensionId, context)
7134
+ );
7135
+ const evidence = evidenceForDiagnostics(
7136
+ diagnostics,
7137
+ diagnosticIndexes,
7138
+ status,
7139
+ dimensionId,
7140
+ context
7141
+ );
7142
+ return {
7143
+ id: dimensionId,
7144
+ status,
7145
+ diagnosticIndexes,
7146
+ ...evidence ? { evidence } : {}
7147
+ };
7148
+ })
7149
+ });
6836
7150
 
6837
7151
  // src/checks/check.ts
6838
7152
  var summarizeDiagnostics = (diagnostics) => {
@@ -6849,12 +7163,23 @@ var runCheck = async (cwd, options = {}) => {
6849
7163
  const branchScope = await getBranchScopeData(rootDir);
6850
7164
  const loadResult = await loadConfig(rootDir);
6851
7165
  if (!loadResult.config) {
7166
+ const scorecard2 = buildTruthHealthScorecard(loadResult.diagnostics, {
7167
+ branchFreshnessRan: false,
7168
+ branchFreshnessNotRunReason: options.base ? "config unavailable" : "base not supplied",
7169
+ routingChecksRan: false,
7170
+ ownershipChecksRan: false,
7171
+ evidenceChecksRan: false,
7172
+ generatedSurfaceChecksRan: false,
7173
+ truthDocStructureChecksRan: false,
7174
+ decisionRationaleChecksRan: false
7175
+ });
6852
7176
  return {
6853
7177
  command: "check",
6854
7178
  summary: summarizeDiagnostics(loadResult.diagnostics),
6855
7179
  diagnostics: loadResult.diagnostics,
6856
7180
  data: {
6857
- branchScope
7181
+ branchScope,
7182
+ scorecard: scorecard2
6858
7183
  }
6859
7184
  };
6860
7185
  }
@@ -6875,6 +7200,7 @@ var runCheck = async (cwd, options = {}) => {
6875
7200
  areas.truthDocumentEntries
6876
7201
  );
6877
7202
  const generatedSurfaces = await checkGeneratedSurfaces(rootDir, loadResult.config);
7203
+ const sourceTraceability = await validateEvidenceReferences(rootDir, areas.truthDocumentPaths);
6878
7204
  const freshness = options.base ? await checkFreshness(rootDir, loadResult.config, areas.truthDocumentPaths, options.base) : null;
6879
7205
  const diagnostics = [
6880
7206
  ...loadResult.diagnostics,
@@ -6884,6 +7210,7 @@ var runCheck = async (cwd, options = {}) => {
6884
7210
  ...areas.diagnostics,
6885
7211
  ...decisionSections,
6886
7212
  ...generatedSurfaces,
7213
+ ...sourceTraceability,
6887
7214
  ...freshness?.diagnostics ?? []
6888
7215
  ];
6889
7216
  const truthVisibility = {
@@ -6898,6 +7225,11 @@ var runCheck = async (cwd, options = {}) => {
6898
7225
  topologyPressureCount: areas.topologyPressureCount,
6899
7226
  freshnessDiagnosticCount: freshness?.diagnostics.length ?? 0
6900
7227
  };
7228
+ const scorecard = buildTruthHealthScorecard(diagnostics, {
7229
+ branchFreshnessRan: Boolean(freshness),
7230
+ branchFreshnessNotRunReason: options.base ? "freshness checker skipped" : "base not supplied",
7231
+ evidenceChecksRan: true
7232
+ });
6901
7233
  return {
6902
7234
  command: "check",
6903
7235
  summary: summarizeDiagnostics(diagnostics),
@@ -6905,6 +7237,7 @@ var runCheck = async (cwd, options = {}) => {
6905
7237
  data: {
6906
7238
  branchScope,
6907
7239
  truthVisibility,
7240
+ scorecard,
6908
7241
  ...freshness ? { impactSet: freshness.impactSet } : {}
6909
7242
  }
6910
7243
  };
@@ -6913,7 +7246,7 @@ var runCheck = async (cwd, options = {}) => {
6913
7246
  // src/context-pack/build.ts
6914
7247
  import fs22 from "fs/promises";
6915
7248
  import path12 from "path";
6916
- import fg9 from "fast-glob";
7249
+ import fg8 from "fast-glob";
6917
7250
  var uniqueSorted2 = (values) => [...new Set(values)].sort();
6918
7251
  var repoRootPrefixes2 = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
6919
7252
  var isGlobReference2 = (referencePath) => /[*?[\]{}()]/u.test(referencePath);
@@ -6933,9 +7266,19 @@ var readIfExists = async (rootDir, filePath) => {
6933
7266
  return null;
6934
7267
  }
6935
7268
  };
6936
- var boundedContent = (filePath, content, warnings) => {
7269
+ var boundContent = (content) => {
6937
7270
  const lines = content.split("\n");
6938
7271
  if (lines.length <= 200) {
7272
+ return { content, truncated: false };
7273
+ }
7274
+ return {
7275
+ content: [...lines.slice(0, 80), "...", ...lines.slice(-40)].join("\n"),
7276
+ truncated: true
7277
+ };
7278
+ };
7279
+ var boundedContent = (filePath, content, warnings) => {
7280
+ const bounded = boundContent(content);
7281
+ if (!bounded.truncated) {
6939
7282
  return { path: filePath, content, truncated: false };
6940
7283
  }
6941
7284
  warnings.push({
@@ -6946,16 +7289,25 @@ var boundedContent = (filePath, content, warnings) => {
6946
7289
  });
6947
7290
  return {
6948
7291
  path: filePath,
6949
- content: [...lines.slice(0, 80), "...", ...lines.slice(-40)].join("\n"),
7292
+ content: bounded.content,
6950
7293
  truncated: true
6951
7294
  };
6952
7295
  };
6953
- var documentsFor = async (rootDir, paths) => {
7296
+ var documentsFor = async (rootDir, paths, warnings) => {
6954
7297
  const documents = [];
6955
7298
  for (const filePath of uniqueSorted2(paths)) {
6956
7299
  const content = await readIfExists(rootDir, filePath);
6957
7300
  if (content !== null) {
6958
- documents.push({ path: filePath, content });
7301
+ const bounded = boundContent(content);
7302
+ if (bounded.truncated) {
7303
+ warnings.push({
7304
+ category: "context-pack",
7305
+ severity: "review",
7306
+ message: `Context truth doc ${filePath} was truncated to fit ContextPack v0 bounds.`,
7307
+ file: filePath
7308
+ });
7309
+ }
7310
+ documents.push({ path: filePath, content: bounded.content, truncated: bounded.truncated });
6959
7311
  }
6960
7312
  }
6961
7313
  return documents;
@@ -6984,7 +7336,7 @@ var sourceOfTruthPathsFor = async (rootDir, docs, truthDocPaths) => {
6984
7336
  }
6985
7337
  if (isGlobReference2(normalizedPath)) {
6986
7338
  sourcePaths.push(
6987
- ...await fg9(normalizedPath, {
7339
+ ...await fg8(normalizedPath, {
6988
7340
  cwd: rootDir,
6989
7341
  dot: true,
6990
7342
  onlyFiles: true,
@@ -6998,12 +7350,12 @@ var sourceOfTruthPathsFor = async (rootDir, docs, truthDocPaths) => {
6998
7350
  }
6999
7351
  return uniqueSorted2(sourcePaths);
7000
7352
  };
7001
- var writePathsFor = (workflow, truthDocs, routes) => {
7353
+ var writePathsFor = (workflow, routeIndexPath, truthDocs, routes) => {
7002
7354
  if (workflow === "truth-sync") {
7003
- return uniqueSorted2(["docs/truthmark/areas.md", ...truthDocs]);
7355
+ return uniqueSorted2([routeIndexPath, ...truthDocs]);
7004
7356
  }
7005
7357
  if (workflow === "truth-document") {
7006
- return uniqueSorted2(["docs/truthmark/areas.md", ...truthDocs]);
7358
+ return uniqueSorted2([routeIndexPath, ...truthDocs]);
7007
7359
  }
7008
7360
  return uniqueSorted2(routes.flatMap((route) => route.codeSurface));
7009
7361
  };
@@ -7013,9 +7365,11 @@ var testCommandsFor = (affectedTests) => {
7013
7365
  var buildContextPack = async (cwd, options) => {
7014
7366
  const repoIndex = await buildRepoIndex(cwd);
7015
7367
  const rootDir = repoIndex.repository.root;
7368
+ const loadResult = await loadConfig(rootDir);
7369
+ const config = loadResult.config ?? (loadResult.status === "missing" ? createDefaultConfig() : null);
7016
7370
  const impactSet = options.base ? await buildImpactSet(rootDir, { base: options.base }) : null;
7017
7371
  const routeMap = impactSet ? repoIndex.routeMap : repoIndex.routeMap;
7018
- const warnings = [];
7372
+ const warnings = loadResult.status === "invalid" ? [...loadResult.diagnostics] : [];
7019
7373
  const truthDocPaths = impactSet?.affectedTruthDocs ?? (options.workflow === "truth-realize" ? [] : routeMap.routes.flatMap((route) => route.truthDocs));
7020
7374
  const contextRoutes = impactSet?.affectedRoutes ?? (options.workflow === "truth-realize" ? [] : routeMap.routes);
7021
7375
  if (options.workflow === "truth-realize" && !impactSet) {
@@ -7036,8 +7390,13 @@ var buildContextPack = async (cwd, options) => {
7036
7390
  base: options.base ?? null,
7037
7391
  impactSet,
7038
7392
  routeMap,
7039
- allowedWritePaths: writePathsFor(options.workflow, truthDocPaths, contextRoutes),
7040
- truthDocs: await documentsFor(rootDir, truthDocPaths),
7393
+ allowedWritePaths: config === null ? [] : writePathsFor(
7394
+ options.workflow,
7395
+ config.truthmark.paths.routesIndex,
7396
+ truthDocPaths,
7397
+ contextRoutes
7398
+ ),
7399
+ truthDocs: await documentsFor(rootDir, truthDocPaths, warnings),
7041
7400
  sourceFiles: await sourceFilesFor(rootDir, sourceFilePaths, warnings),
7042
7401
  testCommands: testCommandsFor(impactSet?.affectedTests ?? []),
7043
7402
  warnings
@@ -7056,7 +7415,7 @@ var renderContextPackMarkdown = (pack) => {
7056
7415
  ...pack.allowedWritePaths.map((filePath) => `- ${filePath}`),
7057
7416
  "",
7058
7417
  "## Truth Docs",
7059
- ...pack.truthDocs.map((doc) => `- ${doc.path}`),
7418
+ ...pack.truthDocs.map((doc) => `- ${doc.path}${doc.truncated ? " (truncated)" : ""}`),
7060
7419
  "",
7061
7420
  "## Source Files",
7062
7421
  ...pack.sourceFiles.map((file) => `- ${file.path}${file.truncated ? " (truncated)" : ""}`),
@@ -7068,6 +7427,198 @@ var renderContextPackMarkdown = (pack) => {
7068
7427
  `;
7069
7428
  };
7070
7429
 
7430
+ // src/workflow-state/build.ts
7431
+ import { execa as execa5 } from "execa";
7432
+
7433
+ // src/workflow-state/action-context.ts
7434
+ var uniqueSorted3 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
7435
+ var helperCommandsFor = (manifestEntry) => (manifestEntry.helpers ?? []).map((helper) => ({
7436
+ id: helper.id,
7437
+ runner: helper.runner,
7438
+ argv: [...helper.command.argv],
7439
+ optional: helper.optional
7440
+ }));
7441
+ var evidenceFor = (manifestEntry) => manifestEntry.requiredGates.filter((gate) => /evidence|ownership|containment/iu.test(gate));
7442
+ var baseContext = (manifestEntry, mode, allowedWritePaths, forbiddenWritePaths, writeLeaseRequired) => ({
7443
+ mode,
7444
+ allowedWritePaths: uniqueSorted3(allowedWritePaths),
7445
+ forbiddenWritePaths: uniqueSorted3(forbiddenWritePaths),
7446
+ stopConditions: [...manifestEntry.negativeTriggers, ...manifestEntry.forbiddenAdjacency],
7447
+ requiredEvidence: evidenceFor(manifestEntry),
7448
+ helperValidationCommands: helperCommandsFor(manifestEntry),
7449
+ writeLeaseRequired
7450
+ });
7451
+ var buildWorkflowActionContext = (manifestEntry, data = {}) => {
7452
+ if (manifestEntry.id === "truthmark-preview" || manifestEntry.id === "truthmark-check") {
7453
+ return baseContext(manifestEntry, "read-only", [], [], false);
7454
+ }
7455
+ if (manifestEntry.id === "truthmark-sync" || manifestEntry.id === "truthmark-document") {
7456
+ return baseContext(
7457
+ manifestEntry,
7458
+ "truth-doc-write",
7459
+ [...data.routeIndexPath ? [data.routeIndexPath] : [], ...data.routeFiles ?? [], ...data.truthDocs ?? []],
7460
+ [],
7461
+ true
7462
+ );
7463
+ }
7464
+ if (manifestEntry.id === "truthmark-structure") {
7465
+ return baseContext(
7466
+ manifestEntry,
7467
+ "route-write",
7468
+ [
7469
+ ...data.routeIndexPath ? [data.routeIndexPath] : [],
7470
+ ...data.routeFiles ?? [],
7471
+ ...data.starterTruthDocs ?? []
7472
+ ],
7473
+ [],
7474
+ true
7475
+ );
7476
+ }
7477
+ if (manifestEntry.id === "truthmark-realize") {
7478
+ return baseContext(
7479
+ manifestEntry,
7480
+ "code-write",
7481
+ data.codeWritePaths ?? [],
7482
+ [
7483
+ ...data.routeIndexPath ? [data.routeIndexPath] : [],
7484
+ ...data.routeFiles ?? [],
7485
+ ...data.truthRoot ? [`${data.truthRoot}/**/*.md`] : [],
7486
+ ...data.truthDocs ?? []
7487
+ ],
7488
+ false
7489
+ );
7490
+ }
7491
+ return baseContext(
7492
+ manifestEntry,
7493
+ "portal-write",
7494
+ data.portalEnabled && data.portalOutputPath ? [`${data.portalOutputPath}/**`] : [],
7495
+ [],
7496
+ false
7497
+ );
7498
+ };
7499
+
7500
+ // src/workflow-state/build.ts
7501
+ var helperCommandsFor2 = (workflow) => (TRUTHMARK_WORKFLOW_MANIFEST[workflow].helpers ?? []).map((helper) => ({
7502
+ id: helper.id,
7503
+ runner: helper.runner,
7504
+ argv: [...helper.command.argv],
7505
+ optional: helper.optional
7506
+ }));
7507
+ var uniqueSorted4 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
7508
+ var isWriteCapable = (workflow) => !["truthmark-preview", "truthmark-check"].includes(workflow);
7509
+ var DEFAULT_BASE_CANDIDATES = ["@{upstream}", "origin/main", "main", "origin/master", "master"];
7510
+ var selectComparisonBase = async (rootDir, suppliedBase) => {
7511
+ if (suppliedBase) {
7512
+ return suppliedBase;
7513
+ }
7514
+ for (const candidate of DEFAULT_BASE_CANDIDATES) {
7515
+ const result = await execa5("git", ["rev-parse", "--verify", `${candidate}^{commit}`], {
7516
+ cwd: rootDir,
7517
+ reject: false
7518
+ });
7519
+ if ((result.exitCode ?? 1) === 0) {
7520
+ return candidate;
7521
+ }
7522
+ }
7523
+ return null;
7524
+ };
7525
+ var routeFilesFor = (repoIndex) => uniqueSorted4(repoIndex.routeMap.routes.map((route) => route.sourcePath));
7526
+ var hasUnmappedFunctionalChange = (impactSet) => impactSet?.diagnostics.some(
7527
+ (diagnostic) => diagnostic.category === "impact" && /not mapped to a Truthmark route|no affected truth document/u.test(diagnostic.message)
7528
+ ) ?? false;
7529
+ var applicabilityFor = (workflow, diagnostics, impactSet) => {
7530
+ const reasons = [];
7531
+ if (diagnostics.some((diagnostic) => diagnostic.message.includes("Missing .truthmark/config.yml"))) {
7532
+ reasons.push("Missing .truthmark/config.yml.");
7533
+ return { state: isWriteCapable(workflow) ? "blocked" : "not_applicable", reasons };
7534
+ }
7535
+ if (workflow === "truthmark-sync" && !impactSet) {
7536
+ reasons.push("truthmark-sync requires --base to derive bounded truth-doc write paths.");
7537
+ return { state: "blocked", reasons };
7538
+ }
7539
+ if (workflow === "truthmark-realize" && !impactSet) {
7540
+ reasons.push("truthmark-realize requires --base to derive bounded allowed write paths.");
7541
+ return { state: "blocked", reasons };
7542
+ }
7543
+ if (hasUnmappedFunctionalChange(impactSet)) {
7544
+ reasons.push("Changed functional files have ambiguous or missing Truthmark route ownership.");
7545
+ return { state: "ambiguous", reasons };
7546
+ }
7547
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
7548
+ reasons.push("Existing diagnostics contain errors that block safe workflow execution.");
7549
+ return { state: "blocked", reasons };
7550
+ }
7551
+ return { state: "applicable", reasons };
7552
+ };
7553
+ var contextDataFor = (workflow, repoIndex, config, impactSet) => {
7554
+ if (!config) {
7555
+ return {};
7556
+ }
7557
+ const routeFiles = routeFilesFor(repoIndex);
7558
+ const truthDocs = uniqueSorted4(
7559
+ impactSet?.affectedTruthDocs ?? repoIndex.routeMap.routes.flatMap((route) => route.truthDocs)
7560
+ );
7561
+ return {
7562
+ routeIndexPath: config.truthmark.paths.routesIndex,
7563
+ routeFiles,
7564
+ truthRoot: config.truthmark.paths.truthRoot,
7565
+ truthDocs,
7566
+ starterTruthDocs: workflow === "truthmark-structure" ? truthDocs : [],
7567
+ codeWritePaths: workflow === "truthmark-realize" ? uniqueSorted4(impactSet?.affectedRoutes.flatMap((route) => route.codeSurface) ?? []) : [],
7568
+ portalEnabled: config.truthmark.generated.portal.enabled,
7569
+ portalOutputPath: config.truthmark.paths.portalOutput,
7570
+ routes: repoIndex.routeMap.routes
7571
+ };
7572
+ };
7573
+ var nextStepsFor = (workflow, applicability, comparisonBase) => {
7574
+ if (applicability.state === "ambiguous") {
7575
+ return ["Run Truth Structure or repair route ownership before writing truth docs."];
7576
+ }
7577
+ if ((workflow === "truthmark-sync" || workflow === "truthmark-realize") && applicability.state === "blocked" && !comparisonBase) {
7578
+ return [
7579
+ workflow === "truthmark-sync" ? "Rerun with --base <ref> so Truthmark can derive bounded truth-doc write paths." : "Rerun with --base <ref> so Truthmark can derive bounded allowed code-write paths."
7580
+ ];
7581
+ }
7582
+ return [];
7583
+ };
7584
+ var buildWorkflowState = async (cwd, options) => {
7585
+ const manifestEntry = TRUTHMARK_WORKFLOW_MANIFEST[options.workflow];
7586
+ if (!manifestEntry) {
7587
+ throw new Error(`Unknown Truthmark workflow: ${String(options.workflow)}`);
7588
+ }
7589
+ const repoIndex = await buildRepoIndex(cwd);
7590
+ const rootDir = repoIndex.repository.root;
7591
+ const loadResult = await loadConfig(rootDir);
7592
+ const comparisonBase = options.base ? options.base : options.workflow === "truthmark-sync" ? await selectComparisonBase(rootDir) : null;
7593
+ const impactSet = comparisonBase ? await buildImpactSet(rootDir, { base: comparisonBase }) : null;
7594
+ const checkResult = await runCheck(cwd, comparisonBase ? { base: comparisonBase } : {});
7595
+ const diagnostics = [
7596
+ ...loadResult.diagnostics,
7597
+ ...repoIndex.diagnostics,
7598
+ ...impactSet?.diagnostics ?? [],
7599
+ ...checkResult.diagnostics
7600
+ ];
7601
+ const applicability = applicabilityFor(options.workflow, diagnostics, impactSet);
7602
+ const actionData = applicability.state === "blocked" || applicability.state === "ambiguous" ? {} : contextDataFor(options.workflow, repoIndex, loadResult.config, impactSet);
7603
+ return {
7604
+ schemaVersion: "truthmark-workflow/v0",
7605
+ workflow: options.workflow,
7606
+ applicability,
7607
+ actionContext: buildWorkflowActionContext(manifestEntry, actionData),
7608
+ changedFiles: impactSet?.changedFiles ?? [],
7609
+ affectedRoutes: impactSet?.affectedRoutes ?? [],
7610
+ targetTruthDocs: applicability.state === "ambiguous" ? [] : impactSet?.affectedTruthDocs ?? [],
7611
+ diagnostics,
7612
+ checks: {
7613
+ required: [...manifestEntry.requiredGates],
7614
+ recommended: [...manifestEntry.positiveTriggers],
7615
+ helpers: helperCommandsFor2(options.workflow)
7616
+ },
7617
+ nextSteps: nextStepsFor(options.workflow, applicability, comparisonBase),
7618
+ reportSections: [...manifestEntry.reportSections]
7619
+ };
7620
+ };
7621
+
7071
7622
  // src/cli/handlers.ts
7072
7623
  import fs23 from "fs/promises";
7073
7624
 
@@ -7434,9 +7985,26 @@ var runImpact = async (options) => {
7434
7985
  var isContextPackWorkflow = (value) => {
7435
7986
  return value === "truth-sync" || value === "truth-document" || value === "truth-realize";
7436
7987
  };
7437
- var isContextPackFormat = (value) => {
7438
- return value === void 0 || value === "json" || value === "markdown";
7988
+ var isContextMarkdownFormat = (value) => {
7989
+ return value === void 0 || value === "markdown";
7439
7990
  };
7991
+ var isTruthmarkWorkflowId = (value) => {
7992
+ return typeof value === "string" && TRUTHMARK_WORKFLOW_IDS.includes(value);
7993
+ };
7994
+ var invalidWorkflowResult = (command, workflow) => ({
7995
+ command,
7996
+ summary: workflow ? `Truthmark workflow requires a supported full workflow ID; received ${workflow}.` : "Truthmark workflow requires --workflow.",
7997
+ diagnostics: [
7998
+ {
7999
+ category: "workflow-state",
8000
+ severity: "error",
8001
+ message: workflow ? `Unknown Truthmark workflow: ${workflow}. Use a canonical full manifest ID such as truthmark-sync or truthmark-check.` : `truthmark ${command} requires --workflow <workflow>.`
8002
+ }
8003
+ ],
8004
+ data: {
8005
+ request: workflow ? { workflow } : {}
8006
+ }
8007
+ });
7440
8008
  var readHelperFile = async (filePath, helper) => {
7441
8009
  try {
7442
8010
  return await fs23.readFile(filePath, "utf8");
@@ -7478,7 +8046,20 @@ var runContext = async (options) => {
7478
8046
  ]
7479
8047
  };
7480
8048
  }
7481
- if (!isContextPackFormat(options.format)) {
8049
+ if (options.format === "json") {
8050
+ return {
8051
+ command: "context",
8052
+ summary: "Truthmark context no longer supports JSON ContextPack output.",
8053
+ diagnostics: [
8054
+ {
8055
+ category: "context-pack",
8056
+ severity: "error",
8057
+ message: "JSON ContextPack output was removed in v2; use --format markdown."
8058
+ }
8059
+ ]
8060
+ };
8061
+ }
8062
+ if (!isContextMarkdownFormat(options.format)) {
7482
8063
  return {
7483
8064
  command: "context",
7484
8065
  summary: "Truthmark context requires a supported --format value.",
@@ -7486,7 +8067,7 @@ var runContext = async (options) => {
7486
8067
  {
7487
8068
  category: "context-pack",
7488
8069
  severity: "error",
7489
- message: "truthmark context requires --format json or markdown."
8070
+ message: "truthmark context supports only --format markdown; JSON ContextPack output was removed in v2."
7490
8071
  }
7491
8072
  ]
7492
8073
  };
@@ -7496,26 +8077,56 @@ var runContext = async (options) => {
7496
8077
  base: options.base
7497
8078
  });
7498
8079
  const diagnostics = contextPack.warnings;
8080
+ const summary = `Truthmark context generated ${contextPack.workflow} ContextPack with ${diagnostics.length} warnings.`;
8081
+ const markdown = renderContextPackMarkdown(contextPack);
7499
8082
  return {
7500
8083
  command: "context",
7501
- summary: `Truthmark context generated ${contextPack.workflow} ContextPack with ${diagnostics.length} warnings.`,
8084
+ summary,
7502
8085
  diagnostics,
7503
8086
  data: {
7504
- contextPack,
7505
- ...options.format === "markdown" ? { markdown: renderContextPackMarkdown(contextPack) } : {}
8087
+ markdown,
8088
+ summary
8089
+ }
8090
+ };
8091
+ };
8092
+ var runWorkflowStatus = async (options) => {
8093
+ if (!isTruthmarkWorkflowId(options.workflow)) {
8094
+ return invalidWorkflowResult("workflow status", options.workflow);
8095
+ }
8096
+ const workflowState = await buildWorkflowState(process.cwd(), {
8097
+ workflow: options.workflow,
8098
+ ...options.base ? { base: options.base } : {}
8099
+ });
8100
+ return {
8101
+ command: "workflow status",
8102
+ summary: `Truthmark workflow status completed for ${options.workflow}.`,
8103
+ diagnostics: workflowState.diagnostics,
8104
+ data: {
8105
+ request: {
8106
+ workflow: options.workflow,
8107
+ ...options.base ? { base: options.base } : {}
8108
+ },
8109
+ workflowState
7506
8110
  }
7507
8111
  };
7508
8112
  };
7509
8113
 
7510
8114
  // src/cli/program.ts
8115
+ var markFailedWhenErrorDiagnosticsExist = (result) => {
8116
+ if (result.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
8117
+ process.exitCode = 1;
8118
+ }
8119
+ };
7511
8120
  var writeResult = (result, options) => {
7512
8121
  const output = options.json ? renderJson(result) : renderHuman(result);
7513
8122
  process.stdout.write(`${output}
7514
8123
  `);
8124
+ markFailedWhenErrorDiagnosticsExist(result);
7515
8125
  };
7516
8126
  var writeContextResult = (result, options) => {
7517
- if (!options.json && options.format === "markdown" && typeof result.data?.markdown === "string") {
8127
+ if (!options.json && typeof result.data?.markdown === "string") {
7518
8128
  process.stdout.write(result.data.markdown);
8129
+ markFailedWhenErrorDiagnosticsExist(result);
7519
8130
  return;
7520
8131
  }
7521
8132
  writeResult(result, options);
@@ -7574,7 +8185,7 @@ var buildProgram = () => {
7574
8185
  writeResult(await runImpact({ base: options.base }), options);
7575
8186
  });
7576
8187
  addJsonOption(
7577
- program.command("context").description("Generate a bounded workflow context pack.").requiredOption("--workflow <workflow>", "Workflow name: truth-sync, truth-document, or truth-realize").option("--base <ref>", "Base Git ref for impact-backed packs").option("--format <format>", "Output format: json or markdown", "json")
8188
+ program.command("context").description("Generate a bounded workflow context pack.").requiredOption("--workflow <workflow>", "Workflow name: truth-sync, truth-document, or truth-realize").option("--base <ref>", "Base Git ref for impact-backed packs").option("--format <format>", "Output format: markdown", "markdown")
7578
8189
  ).action(async (options) => {
7579
8190
  writeContextResult(
7580
8191
  await runContext({
@@ -7585,6 +8196,18 @@ var buildProgram = () => {
7585
8196
  options
7586
8197
  );
7587
8198
  });
8199
+ const workflow = program.command("workflow").description("Inspect agent-facing Truthmark workflow state.");
8200
+ addJsonOption(
8201
+ workflow.command("status").description("Return schema-versioned workflow state for a canonical workflow ID.").option("--workflow <workflow>", "Canonical workflow ID, such as truthmark-sync").option("--base <ref>", "Base Git ref for impact-backed workflow state")
8202
+ ).action(async (options) => {
8203
+ writeResult(
8204
+ await runWorkflowStatus({
8205
+ workflow: options.workflow,
8206
+ base: options.base
8207
+ }),
8208
+ options
8209
+ );
8210
+ });
7588
8211
  const validate = program.command("validate").description("Run optional Truthmark workflow helper validators from the installed CLI.");
7589
8212
  addJsonOption(
7590
8213
  validate.command("sync-report").description("Validate a Truth Sync report file.").argument("<report-file>", "Truth Sync report file")