truthmark 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -334,6 +334,22 @@ var truthmarkConfigSchema = {
334
334
  type: "string"
335
335
  }
336
336
  },
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
+ },
337
353
  frontmatter: {
338
354
  type: "object",
339
355
  nullable: true,
@@ -391,6 +407,11 @@ var DEFAULT_AUTHORITY = [
391
407
  `${DEFAULT_DOCS_HIERARCHY.roots.truth}/**/*.md`
392
408
  ];
393
409
  var DEFAULT_INSTRUCTION_TARGETS = ["AGENTS.md"];
410
+ var DEFAULT_TRUTHMARK_PORTAL = {
411
+ enabled: false,
412
+ output: "docs/truthmark-portal",
413
+ template: "default"
414
+ };
394
415
  var createDefaultRawConfig = () => ({
395
416
  version: 1,
396
417
  platforms: [...DEFAULT_PLATFORMS],
@@ -422,6 +443,7 @@ var createDefaultConfig = () => ({
422
443
  },
423
444
  authority: [...DEFAULT_AUTHORITY],
424
445
  instructionTargets: [...DEFAULT_INSTRUCTION_TARGETS],
446
+ truthmarkPortal: { ...DEFAULT_TRUTHMARK_PORTAL },
425
447
  frontmatter: {
426
448
  required: [],
427
449
  recommended: ["status", "doc_type", "last_reviewed", "source_of_truth"]
@@ -578,9 +600,9 @@ var parseTruthDocumentsFromYaml = (sectionLines, areaName) => {
578
600
  const diagnostics = [];
579
601
  const truthDocumentEntries = [];
580
602
  for (const rawEntry of rawEntries) {
581
- const path12 = rawEntry && typeof rawEntry === "object" && "path" in rawEntry ? rawEntry.path : null;
603
+ const path13 = rawEntry && typeof rawEntry === "object" && "path" in rawEntry ? rawEntry.path : null;
582
604
  const kind = rawEntry && typeof rawEntry === "object" && "kind" in rawEntry ? rawEntry.kind : null;
583
- if (typeof path12 !== "string" || path12.trim().length === 0 || !isTruthDocumentKind(kind)) {
605
+ if (typeof path13 !== "string" || path13.trim().length === 0 || !isTruthDocumentKind(kind)) {
584
606
  diagnostics.push(
585
607
  createAreaDiagnostic(
586
608
  `Area ${areaName} truth_documents entries must include non-empty path and valid kind fields.`,
@@ -590,7 +612,7 @@ var parseTruthDocumentsFromYaml = (sectionLines, areaName) => {
590
612
  continue;
591
613
  }
592
614
  truthDocumentEntries.push({
593
- path: path12.trim(),
615
+ path: path13.trim(),
594
616
  kind,
595
617
  kindSource: "explicit"
596
618
  });
@@ -862,6 +884,102 @@ var ARCHITECTURE_DOC_TEMPLATE_PATH = "docs/templates/architecture-doc.md";
862
884
  var WORKFLOW_DOC_TEMPLATE_PATH = "docs/templates/workflow-doc.md";
863
885
  var OPERATIONS_DOC_TEMPLATE_PATH = "docs/templates/operations-doc.md";
864
886
  var TEST_BEHAVIOR_DOC_TEMPLATE_PATH = "docs/templates/test-behavior-doc.md";
887
+ var renderTemplateSection = (section) => {
888
+ return [
889
+ section.heading,
890
+ "",
891
+ "<!--",
892
+ ...section.guidance,
893
+ "-->",
894
+ "",
895
+ `{{${section.placeholder}}}`,
896
+ ""
897
+ ];
898
+ };
899
+ var titleToPlaceholder = (title) => {
900
+ return title.replace(/^#+\s+/u, "").toLowerCase().replaceAll(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
901
+ };
902
+ var findTemplateSectionHeadings = (template) => {
903
+ const matches = [];
904
+ let fencedCodeMarker = null;
905
+ let fencedCodeLength = 0;
906
+ for (const lineMatch of template.matchAll(/^.*(?:\r?\n|$)/gm)) {
907
+ const rawLine = lineMatch[0];
908
+ if (rawLine.length === 0) {
909
+ continue;
910
+ }
911
+ const line = rawLine.replace(/\r?\n$/u, "");
912
+ const fenceMatch = /^(?: {0,3})(`{3,}|~{3,})/u.exec(line);
913
+ if (fenceMatch) {
914
+ const marker = fenceMatch[1]?.[0];
915
+ const length = fenceMatch[1]?.length ?? 0;
916
+ if (fencedCodeMarker === null) {
917
+ fencedCodeMarker = marker;
918
+ fencedCodeLength = length;
919
+ } else if (marker === fencedCodeMarker && length >= fencedCodeLength) {
920
+ fencedCodeMarker = null;
921
+ fencedCodeLength = 0;
922
+ }
923
+ continue;
924
+ }
925
+ if (fencedCodeMarker === null && /^## .+$/u.test(line)) {
926
+ matches.push({ heading: line.trim(), index: lineMatch.index });
927
+ }
928
+ }
929
+ return matches;
930
+ };
931
+ var parseTemplateSections = (template) => {
932
+ const matches = findTemplateSectionHeadings(template);
933
+ if (matches.length === 0) {
934
+ return { preamble: template.trimEnd(), sections: [] };
935
+ }
936
+ const sections = matches.map((match, index) => {
937
+ const start = match.index;
938
+ const next = matches[index + 1];
939
+ const end = next?.index ?? template.length;
940
+ return {
941
+ heading: match.heading,
942
+ block: template.slice(start, end).trimEnd()
943
+ };
944
+ });
945
+ return {
946
+ preamble: template.slice(0, matches[0]?.index ?? 0).trimEnd(),
947
+ sections
948
+ };
949
+ };
950
+ var mergeTruthDocTemplate = (existingTemplate, defaultTemplate) => {
951
+ if (existingTemplate.trim().length === 0) {
952
+ return defaultTemplate;
953
+ }
954
+ const defaultParsed = parseTemplateSections(defaultTemplate);
955
+ const existingParsed = parseTemplateSections(existingTemplate);
956
+ const defaultHeadings = new Set(defaultParsed.sections.map((section) => section.heading));
957
+ const customBeforeDefault = /* @__PURE__ */ new Map();
958
+ const trailingCustomSections = [];
959
+ existingParsed.sections.forEach((section, index) => {
960
+ if (defaultHeadings.has(section.heading)) {
961
+ return;
962
+ }
963
+ const nextDefaultSection = existingParsed.sections.slice(index + 1).find((candidate) => defaultHeadings.has(candidate.heading));
964
+ if (nextDefaultSection) {
965
+ const bucket = customBeforeDefault.get(nextDefaultSection.heading) ?? [];
966
+ bucket.push(section);
967
+ customBeforeDefault.set(nextDefaultSection.heading, bucket);
968
+ return;
969
+ }
970
+ trailingCustomSections.push(section);
971
+ });
972
+ const mergedSections = defaultParsed.sections.flatMap((section) => [
973
+ ...customBeforeDefault.get(section.heading) ?? [],
974
+ section
975
+ ]);
976
+ return [
977
+ existingParsed.preamble,
978
+ ...mergedSections.map((section) => section.block),
979
+ ...trailingCustomSections.map((section) => section.block),
980
+ ""
981
+ ].filter((block) => block.length > 0).join("\n\n");
982
+ };
865
983
  var renderBehaviorDocTemplateFile = () => {
866
984
  return [
867
985
  "---",
@@ -877,81 +995,130 @@ var renderBehaviorDocTemplateFile = () => {
877
995
  "",
878
996
  "## Purpose",
879
997
  "",
880
- "<!-- 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. -->",
998
+ "<!--",
999
+ "State the user/system outcome this behavior protects and why it exists.",
1000
+ "Include the problem boundary and durable value; exclude roadmap, implementation plan, and historical narrative.",
1001
+ "List the code, config, docs, or tests that support the claim in source_of_truth rather than prose-only assertion.",
1002
+ "-->",
881
1003
  "",
882
1004
  "{{purpose}}",
883
1005
  "",
884
1006
  "## Scope",
885
1007
  "",
886
- "{{scope}}",
887
- "",
888
1008
  "<!--",
889
- "This doc must own one coherent behavior surface.",
890
- "Split into another leaf doc when content introduces:",
891
- "- a distinct user or system outcome",
892
- "- a separate lifecycle or state machine",
893
- "- an unrelated rule family",
894
- "- a different external contract",
895
- "- code that should route through a different owner",
1009
+ "Define the one coherent behavior surface this document owns.",
1010
+ "Include in-scope actors, entrypoints, state/data owned by this doc, and explicit handoffs to neighboring truth docs.",
1011
+ "Split into another leaf doc when content introduces a distinct outcome, state machine, rule family, external contract, or route owner.",
896
1012
  "Keep README.md files as indexes only.",
897
1013
  "-->",
898
1014
  "",
1015
+ "{{scope}}",
1016
+ "",
899
1017
  "This doc was created from the editable behavior-doc template at {{template_path}}.",
900
1018
  "",
901
1019
  "## Current Behavior",
902
1020
  "",
903
- "<!-- Describe implemented behavior in present tense. Do not include desired future behavior. -->",
1021
+ "<!--",
1022
+ "Describe only current implemented behavior in present tense.",
1023
+ "Cover observable behavior, important defaults, and user/system-visible effects; exclude desired future behavior and speculative design.",
1024
+ "Every non-obvious claim should be checkable from source_of_truth evidence.",
1025
+ "-->",
904
1026
  "",
905
1027
  "{{current_behavior}}",
906
1028
  "",
907
1029
  "## Core Rules",
908
1030
  "",
909
- "<!-- Capture stable business rules, invariants, precedence rules, validation rules, and must-never constraints. Omit incidental implementation details. -->",
1031
+ "<!--",
1032
+ "Capture stable business rules, invariants, precedence rules, validation rules, and must-never constraints.",
1033
+ "Separate rules from incidental implementation details; cite current implementation or tests for rule enforcement.",
1034
+ "-->",
910
1035
  "",
911
1036
  "{{core_rules}}",
912
1037
  "",
913
1038
  "## Flows And States",
914
1039
  "",
915
- "<!-- 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. -->",
1040
+ "<!--",
1041
+ "Document state transitions, lifecycle stages, retries, fallbacks, route switches, and important error paths.",
1042
+ "State 'None beyond current behavior.' when this behavior has no distinct flow or state model.",
1043
+ "-->",
916
1044
  "",
917
1045
  "{{flows_and_states}}",
918
1046
  "",
919
1047
  "## Contracts",
920
1048
  "",
921
- "<!-- 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. -->",
1049
+ "<!--",
1050
+ "Capture user-visible or integration contracts: CLI/API shape, inputs, outputs, diagnostics, files, events, permissions, or links to canonical contract docs.",
1051
+ "Avoid duplicating a separate canonical contract doc; link to it when contract ownership lives elsewhere.",
1052
+ "-->",
922
1053
  "",
923
1054
  "{{contracts}}",
924
1055
  "",
925
1056
  "## Product Decisions",
926
1057
  "",
927
- "<!-- Keep active decisions only. Replace stale decisions instead of appending historical logs. -->",
1058
+ "<!--",
1059
+ "Keep active decisions only, dated inline when added or changed.",
1060
+ "Explain decisions that shape behavior, boundaries, rejected alternatives, or migration constraints; replace stale decisions instead of appending historical logs.",
1061
+ "-->",
928
1062
  "",
929
1063
  "{{decision}}",
930
1064
  "",
931
1065
  "## Rationale",
932
1066
  "",
933
- "<!-- Explain why the current behavior and active decisions are this way, including tradeoffs. -->",
1067
+ "<!--",
1068
+ "Explain why the current behavior and active decisions are this way, including tradeoffs and constraints.",
1069
+ "Tie rationale to evidence-backed behavior; do not use this as a changelog.",
1070
+ "-->",
934
1071
  "",
935
1072
  "{{rationale}}",
936
1073
  "",
937
1074
  "## Non-Goals",
938
1075
  "",
939
- "<!-- Name adjacent behavior this doc intentionally does not own, especially tempting future expansions. -->",
1076
+ "<!--",
1077
+ "Name adjacent behavior this doc intentionally does not own, especially tempting future expansions or neighboring route owners.",
1078
+ "Use this section to prevent scope creep and duplicate truth ownership.",
1079
+ "-->",
940
1080
  "",
941
1081
  "{{non_goals}}",
942
1082
  "",
943
1083
  "## Maintenance Notes",
944
1084
  "",
945
- "<!-- List related tests, routing cautions, migration notes, and common drift risks for future agents. Keep this operational, not historical. -->",
1085
+ "<!--",
1086
+ "List related tests, routing cautions, migration notes, evidence drift risks, and review triggers for future maintainers or agents.",
1087
+ "Keep this operational and current-state focused, not historical.",
1088
+ "-->",
946
1089
  "",
947
1090
  "{{maintenance_notes}}",
948
1091
  ""
949
1092
  ].join("\n");
950
1093
  };
1094
+ var sectionSpec = (heading, guidance, placeholder = titleToPlaceholder(heading)) => ({ heading, guidance, placeholder });
1095
+ var PURPOSE_SECTION = sectionSpec("## Purpose", [
1096
+ "State the software-engineering outcome this document protects and why the documented surface exists.",
1097
+ "Include durable value, impacted users/systems, and the problem boundary; exclude roadmap, implementation plans, and historical narrative.",
1098
+ "Keep claims traceable to source_of_truth evidence rather than prose-only assertion."
1099
+ ]);
1100
+ var SCOPE_SECTION = sectionSpec("## Scope", [
1101
+ "Define the one coherent surface this document owns, including actors, entrypoints, owned state/data, and handoffs to neighboring truth docs.",
1102
+ "Call out important out-of-scope boundaries here or in Non-Goals; split the doc when it mixes distinct outcomes, lifecycles, contracts, or owners."
1103
+ ]);
1104
+ var PRODUCT_DECISIONS_SECTION = sectionSpec("## Product Decisions", [
1105
+ "Keep active decisions only, dated inline when added or changed.",
1106
+ "Capture decisions that shape behavior, interfaces, boundaries, compatibility, risk acceptance, or migration constraints.",
1107
+ "Replace stale decisions instead of appending historical logs."
1108
+ ], "decision");
1109
+ var RATIONALE_SECTION = sectionSpec("## Rationale", [
1110
+ "Explain why the current behavior, structure, or contract is this way, including tradeoffs and constraints.",
1111
+ "Tie rationale to evidence-backed facts and active decisions; do not use this as a changelog."
1112
+ ]);
1113
+ var NON_GOALS_SECTION = sectionSpec("## Non-Goals", [
1114
+ "Name adjacent behavior, responsibilities, interfaces, or future expansions this doc intentionally does not own.",
1115
+ "Use this section to prevent scope creep and duplicate truth ownership."
1116
+ ]);
1117
+ var MAINTENANCE_NOTES_SECTION = sectionSpec("## Maintenance Notes", [
1118
+ "List related tests, routing cautions, migration notes, compatibility risks, evidence drift risks, and review triggers for future maintainers or agents.",
1119
+ "Keep this operational and current-state focused, not historical."
1120
+ ]);
951
1121
  var renderTypedTruthDocTemplate = (truthKind, docType, title, sections) => {
952
- const placeholderNameForSection = (section) => {
953
- return section.replace(/^#+\s+/u, "").toLowerCase().replaceAll(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
954
- };
955
1122
  return [
956
1123
  "---",
957
1124
  "status: active",
@@ -964,86 +1131,153 @@ var renderTypedTruthDocTemplate = (truthKind, docType, title, sections) => {
964
1131
  "",
965
1132
  `# ${title}`,
966
1133
  "",
967
- "## Purpose",
968
- "",
969
- "{{purpose}}",
970
- "",
971
- "## Scope",
972
- "",
973
- "{{scope}}",
974
- "",
975
- ...sections.flatMap((section) => [
976
- section,
977
- "",
978
- `{{${placeholderNameForSection(section)}}}`,
979
- ""
980
- ]),
981
- "## Product Decisions",
982
- "",
983
- "{{decision}}",
984
- "",
985
- "## Rationale",
986
- "",
987
- "{{rationale}}",
988
- "",
989
- "## Non-Goals",
990
- "",
991
- "{{non_goals}}",
992
- "",
993
- "## Maintenance Notes",
994
- "",
995
- "{{maintenance_notes}}",
996
- ""
1134
+ ...renderTemplateSection(PURPOSE_SECTION),
1135
+ ...renderTemplateSection(SCOPE_SECTION),
1136
+ ...sections.flatMap(renderTemplateSection),
1137
+ ...renderTemplateSection(PRODUCT_DECISIONS_SECTION),
1138
+ ...renderTemplateSection(RATIONALE_SECTION),
1139
+ ...renderTemplateSection(NON_GOALS_SECTION),
1140
+ ...renderTemplateSection(MAINTENANCE_NOTES_SECTION)
997
1141
  ].join("\n");
998
1142
  };
999
1143
  var renderContractDocTemplateFile = () => {
1000
1144
  return renderTypedTruthDocTemplate("contract", "contract", "{{title}}", [
1001
- "## Contract Surface",
1002
- "## Inputs",
1003
- "## Outputs",
1004
- "## Errors And Diagnostics",
1005
- "## Compatibility Rules",
1006
- "## Versioning And Migration"
1145
+ sectionSpec("## Contract Surface", [
1146
+ "Identify the owned API, CLI, file format, event, protocol, permission boundary, or integration surface.",
1147
+ "State consumers/producers, stability level, and the source files/tests that define the contract."
1148
+ ]),
1149
+ sectionSpec("## Inputs", [
1150
+ "Document accepted parameters, payloads, files, environment/config keys, permissions, and validation rules.",
1151
+ "Include required/optional status, defaults, constraints, and normalization behavior."
1152
+ ]),
1153
+ sectionSpec("## Outputs", [
1154
+ "Document returned values, emitted files/events, state changes, side effects, and success diagnostics.",
1155
+ "Make externally observable behavior explicit enough for compatibility review."
1156
+ ]),
1157
+ sectionSpec("## Errors And Diagnostics", [
1158
+ "List error classes, exit/status codes, user-facing diagnostics, retries, and recoverability expectations.",
1159
+ "Distinguish validation errors, dependency failures, authorization failures, and internal faults when applicable."
1160
+ ]),
1161
+ sectionSpec("## Compatibility Rules", [
1162
+ "State backward/forward compatibility guarantees, tolerated inputs, deprecation rules, and breaking-change triggers.",
1163
+ "Include compatibility tests or review gates that protect the contract."
1164
+ ]),
1165
+ sectionSpec("## Versioning And Migration", [
1166
+ "Document version negotiation, schema/API version fields, rollout requirements, migration steps, and rollback expectations.",
1167
+ "State 'Not versioned' only when the implementation truly has no versioning or migration surface."
1168
+ ])
1007
1169
  ]);
1008
1170
  };
1009
1171
  var renderArchitectureDocTemplateFile = () => {
1010
1172
  return renderTypedTruthDocTemplate("architecture", "architecture", "{{title}}", [
1011
- "## System Role",
1012
- "## Boundaries",
1013
- "## Components",
1014
- "## Data And Control Flow",
1015
- "## Ownership",
1016
- "## Cross-Cutting Constraints"
1173
+ sectionSpec("## System Role", [
1174
+ "Describe the current architectural role of this subsystem/component in the larger system.",
1175
+ "State the primary responsibilities, consumers, providers, and why this boundary exists now."
1176
+ ]),
1177
+ sectionSpec("## Boundaries", [
1178
+ "Define owned code/config/data, external dependencies, trust boundaries, and interfaces crossed by this architecture.",
1179
+ "Name what is deliberately outside the boundary and link neighboring architecture or contract docs when they own it."
1180
+ ]),
1181
+ sectionSpec("## Components", [
1182
+ "List the major runtime/build-time components, modules, services, jobs, or generated artifacts and their responsibilities.",
1183
+ "Keep the component list current and evidence-backed; avoid speculative target architecture."
1184
+ ]),
1185
+ sectionSpec("## Data And Control Flow", [
1186
+ "Describe important data movement, command/control paths, synchronization points, state ownership, and failure paths.",
1187
+ "Call out persistence, queues, caches, external calls, and security-sensitive transitions where relevant."
1188
+ ]),
1189
+ sectionSpec("## Ownership", [
1190
+ "Document team/module ownership, review responsibility, operational responsibility, and escalation paths if known.",
1191
+ "If ownership is inferred from codeowners, config, or repository structure, cite that evidence."
1192
+ ]),
1193
+ sectionSpec("## Cross-Cutting Constraints", [
1194
+ "Record active constraints such as security, privacy, reliability, performance, portability, maintainability, compliance, and cost.",
1195
+ "Tie constraints to source evidence, tests, standards, or operational requirements where available."
1196
+ ])
1017
1197
  ]);
1018
1198
  };
1019
1199
  var renderWorkflowDocTemplateFile = () => {
1020
1200
  return renderTypedTruthDocTemplate("workflow", "behavior", "{{title}}", [
1021
- "## Triggers",
1022
- "## Inputs",
1023
- "## Execution Model",
1024
- "## Steps",
1025
- "## State, Retry, And Failure Behavior",
1026
- "## Outputs"
1201
+ sectionSpec("## Triggers", [
1202
+ "List events, commands, schedules, user actions, webhooks, or dependency signals that start this workflow.",
1203
+ "Include preconditions, authorization requirements, debounce/coalescing behavior, and disabled states when applicable."
1204
+ ]),
1205
+ sectionSpec("## Inputs", [
1206
+ "Document data, files, config, context, credentials, and environmental assumptions consumed by the workflow.",
1207
+ "Include validation, defaults, and normalization that happen before execution."
1208
+ ]),
1209
+ sectionSpec("## Execution Model", [
1210
+ "Describe synchronous/asynchronous execution, concurrency, locking, leases, batching, ordering, and idempotency behavior.",
1211
+ "State whether the workflow is user-blocking, background, distributed, or delegated to another system."
1212
+ ]),
1213
+ sectionSpec("## Steps", [
1214
+ "Capture the current ordered steps or phases at a level useful for maintenance and review.",
1215
+ "Reference implementation entrypoints instead of duplicating line-by-line code behavior."
1216
+ ]),
1217
+ sectionSpec("## State, Retry, And Failure Behavior", [
1218
+ "Document state transitions, retries, timeouts, compensation, fallback, partial-success, and terminal-failure behavior.",
1219
+ "Make externally visible failure semantics and recovery responsibilities clear."
1220
+ ]),
1221
+ sectionSpec("## Outputs", [
1222
+ "List artifacts, state changes, notifications, logs, metrics, diagnostics, and downstream triggers produced by the workflow.",
1223
+ "Include success criteria and handoff points to other truth docs or systems."
1224
+ ])
1027
1225
  ]);
1028
1226
  };
1029
1227
  var renderOperationsDocTemplateFile = () => {
1030
1228
  return renderTypedTruthDocTemplate("operations", "behavior", "{{title}}", [
1031
- "## Operational Surface",
1032
- "## Runtime Topology",
1033
- "## Configuration",
1034
- "## Permissions",
1035
- "## Deployment And Rollback",
1036
- "## Availability And Observability"
1229
+ sectionSpec("## Operational Surface", [
1230
+ "Describe what operators, maintainers, or automated systems can observe or control for this surface.",
1231
+ "Include commands, dashboards, alerts, runbooks, jobs, or operational APIs that define current operations."
1232
+ ]),
1233
+ sectionSpec("## Runtime Topology", [
1234
+ "Document services, processes, containers, hosts, regions, dependencies, queues, stores, and network boundaries involved at runtime.",
1235
+ "State single-node/local behavior explicitly when there is no distributed topology."
1236
+ ]),
1237
+ sectionSpec("## Configuration", [
1238
+ "List operational config, environment variables, feature flags, secrets references, defaults, and reload/restart requirements.",
1239
+ "Do not include secret values; describe storage and rotation expectations instead."
1240
+ ]),
1241
+ sectionSpec("## Permissions", [
1242
+ "Document required identities, roles, scopes, filesystem/network permissions, and least-privilege boundaries.",
1243
+ "Include user-facing authorization behavior and operator access requirements when relevant."
1244
+ ]),
1245
+ sectionSpec("## Deployment And Rollback", [
1246
+ "Describe deployment mechanism, migration ordering, compatibility windows, rollback path, and known irreversible operations.",
1247
+ "Call out manual gates, smoke checks, and post-deploy verification responsibilities."
1248
+ ]),
1249
+ sectionSpec("## Availability And Observability", [
1250
+ "Capture availability expectations, health checks, metrics, logs, traces, alerts, SLO/error-budget signals, and known blind spots.",
1251
+ "Include what maintainers should inspect first during incidents or degraded behavior."
1252
+ ])
1037
1253
  ]);
1038
1254
  };
1039
1255
  var renderTestBehaviorDocTemplateFile = () => {
1040
1256
  return renderTypedTruthDocTemplate("test-behavior", "behavior", "{{title}}", [
1041
- "## Test Surface",
1042
- "## Fixtures And Data Model",
1043
- "## Execution Model",
1044
- "## Assertions And Invariants",
1045
- "## Isolation Rules",
1046
- "## Reporting And Failure Semantics"
1257
+ sectionSpec("## Test Surface", [
1258
+ "Define the behavior, contract, architecture, or workflow surface these tests verify.",
1259
+ "Link the canonical truth docs and code paths the tests are meant to protect."
1260
+ ]),
1261
+ sectionSpec("## Fixtures And Data Model", [
1262
+ "Document fixtures, factories, seeds, mocks/fakes, test repositories, external-service substitutes, and data lifecycle rules.",
1263
+ "Include cleanup, determinism, privacy, and cross-test contamination constraints."
1264
+ ]),
1265
+ sectionSpec("## Execution Model", [
1266
+ "Describe how tests run: command, framework, parallelism, isolation, network/filesystem assumptions, and required services.",
1267
+ "State whether tests are unit, integration, e2e, contract, smoke, regression, or generated checks."
1268
+ ]),
1269
+ sectionSpec("## Assertions And Invariants", [
1270
+ "List the critical assertions, invariants, failure modes, and negative cases that make the tests meaningful.",
1271
+ "Tie assertions to product/contract rules rather than incidental implementation details."
1272
+ ]),
1273
+ sectionSpec("## Isolation Rules", [
1274
+ "Document transaction boundaries, temp directories, fake clocks, network blocking, shared resources, and teardown rules.",
1275
+ "Call out known order dependencies or flake risks and how they are controlled."
1276
+ ]),
1277
+ sectionSpec("## Reporting And Failure Semantics", [
1278
+ "Describe diagnostics, snapshots, logs, coverage signals, retry policy, and how maintainers should interpret failures.",
1279
+ "Include escalation or quarantine criteria for flaky or environment-sensitive tests."
1280
+ ])
1047
1281
  ]);
1048
1282
  };
1049
1283
  var renderTemplate = (template, values) => {
@@ -1160,6 +1394,7 @@ import fs7 from "fs/promises";
1160
1394
 
1161
1395
  // src/config/load.ts
1162
1396
  import fs4 from "fs/promises";
1397
+ import path4 from "path";
1163
1398
  import { Ajv } from "ajv";
1164
1399
  import { parse as parse2 } from "yaml";
1165
1400
  var ajv = new Ajv({ allErrors: true });
@@ -1172,6 +1407,69 @@ var toConfigDiagnostic = (message, file) => {
1172
1407
  file
1173
1408
  };
1174
1409
  };
1410
+ var normalizeRepoRelativePath = (value) => {
1411
+ const slashNormalized = value.replace(/\\/gu, "/");
1412
+ const pathNormalized = path4.posix.normalize(slashNormalized).replace(/\/+$/u, "");
1413
+ return pathNormalized;
1414
+ };
1415
+ var isUnsafeRepoRelativePath = (value) => {
1416
+ const slashNormalized = value.replace(/\\/gu, "/");
1417
+ const normalized = normalizeRepoRelativePath(value);
1418
+ const parts = slashNormalized.split("/");
1419
+ 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("..");
1420
+ };
1421
+ var pathsOverlap = (left, right) => {
1422
+ const normalizedLeft = normalizeRepoRelativePath(left);
1423
+ const normalizedRight = normalizeRepoRelativePath(right);
1424
+ return normalizedLeft === normalizedRight || normalizedLeft.startsWith(`${normalizedRight}/`) || normalizedRight.startsWith(`${normalizedLeft}/`);
1425
+ };
1426
+ var portalForbiddenOutputRoots = (rawConfig) => {
1427
+ const rawDocs = rawConfig.docs;
1428
+ const docsRoots = rawDocs?.roots ?? {};
1429
+ const routing = rawDocs?.routing ?? DEFAULT_DOCS_HIERARCHY.routing;
1430
+ return [
1431
+ "src",
1432
+ DEFAULT_DOCS_HIERARCHY.roots.ai,
1433
+ DEFAULT_DOCS_HIERARCHY.roots.standards,
1434
+ DEFAULT_DOCS_HIERARCHY.roots.architecture,
1435
+ DEFAULT_DOCS_HIERARCHY.roots.truth,
1436
+ ...Object.values(docsRoots),
1437
+ routing.root_index,
1438
+ routing.area_files_root,
1439
+ ".truthmark/config.yml",
1440
+ "AGENTS.md",
1441
+ "CLAUDE.md",
1442
+ "GEMINI.md",
1443
+ ".github/copilot-instructions.md",
1444
+ ...rawConfig.instruction_targets ?? DEFAULT_INSTRUCTION_TARGETS
1445
+ ];
1446
+ };
1447
+ var validatePortalConfig = (rawConfig, configPath) => {
1448
+ const portal = rawConfig["truthmark-portal"];
1449
+ if (portal === void 0) {
1450
+ return [];
1451
+ }
1452
+ const diagnostics = [];
1453
+ const output = portal.output ?? DEFAULT_TRUTHMARK_PORTAL.output;
1454
+ const template = portal.template ?? DEFAULT_TRUTHMARK_PORTAL.template;
1455
+ if (isUnsafeRepoRelativePath(output) || portalForbiddenOutputRoots(rawConfig).some((forbidden) => pathsOverlap(output, forbidden))) {
1456
+ diagnostics.push(
1457
+ toConfigDiagnostic(
1458
+ "truthmark-portal.output must be a non-empty repo-relative directory that does not overlap source, instruction, routing, or canonical docs roots.",
1459
+ configPath
1460
+ )
1461
+ );
1462
+ }
1463
+ if (template !== "default" && isUnsafeRepoRelativePath(template)) {
1464
+ diagnostics.push(
1465
+ toConfigDiagnostic(
1466
+ "truthmark-portal.template must be 'default' or a non-empty repo-relative template path without absolute or parent traversal segments.",
1467
+ configPath
1468
+ )
1469
+ );
1470
+ }
1471
+ return diagnostics;
1472
+ };
1175
1473
  var normalizeConfig = (rawConfig) => {
1176
1474
  const rawDocs = rawConfig.docs ?? {
1177
1475
  layout: DEFAULT_DOCS_HIERARCHY.layout,
@@ -1194,6 +1492,11 @@ var normalizeConfig = (rawConfig) => {
1194
1492
  },
1195
1493
  authority: rawConfig.authority,
1196
1494
  instructionTargets: rawConfig.instruction_targets ?? [...DEFAULT_INSTRUCTION_TARGETS],
1495
+ truthmarkPortal: {
1496
+ enabled: rawConfig["truthmark-portal"]?.enabled ?? DEFAULT_TRUTHMARK_PORTAL.enabled,
1497
+ output: rawConfig["truthmark-portal"]?.output ?? DEFAULT_TRUTHMARK_PORTAL.output,
1498
+ template: rawConfig["truthmark-portal"]?.template ?? DEFAULT_TRUTHMARK_PORTAL.template
1499
+ },
1197
1500
  frontmatter: {
1198
1501
  required: rawConfig.frontmatter?.required ?? [],
1199
1502
  recommended: rawConfig.frontmatter?.recommended ?? []
@@ -1247,6 +1550,18 @@ var loadConfig = async (rootDir) => {
1247
1550
  configPath
1248
1551
  };
1249
1552
  }
1553
+ const portalDiagnostics = validatePortalConfig(
1554
+ parsedConfig,
1555
+ configPath
1556
+ );
1557
+ if (portalDiagnostics.length > 0) {
1558
+ return {
1559
+ status: "invalid",
1560
+ config: null,
1561
+ diagnostics: portalDiagnostics,
1562
+ configPath
1563
+ };
1564
+ }
1250
1565
  return {
1251
1566
  status: "loaded",
1252
1567
  config: normalizeConfig(parsedConfig),
@@ -1291,6 +1606,15 @@ var readBehaviorDocTemplate = async (rootDir) => {
1291
1606
  throw error;
1292
1607
  }
1293
1608
  };
1609
+ var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTemplate) => {
1610
+ const seededResult = await ensureRepoFile(rootDir, templatePath, defaultTemplate);
1611
+ if (seededResult.status !== "unchanged") {
1612
+ return seededResult;
1613
+ }
1614
+ const existingTemplate = await fs5.readFile(resolveRepoPath(rootDir, templatePath), "utf8");
1615
+ const mergedTemplate = mergeTruthDocTemplate(existingTemplate, defaultTemplate);
1616
+ return writeRepoFile(rootDir, templatePath, mergedTemplate);
1617
+ };
1294
1618
  var scaffoldHierarchy = async (rootDir, config) => {
1295
1619
  const results = [];
1296
1620
  const truthDocsRoot = truthRoot2(config);
@@ -1321,26 +1645,42 @@ var scaffoldHierarchy = async (rootDir, config) => {
1321
1645
  )
1322
1646
  );
1323
1647
  results.push(
1324
- await ensureRepoFile(rootDir, BEHAVIOR_DOC_TEMPLATE_PATH, renderBehaviorDocTemplateFile())
1648
+ await ensureOrUpdateTruthDocTemplate(
1649
+ rootDir,
1650
+ BEHAVIOR_DOC_TEMPLATE_PATH,
1651
+ renderBehaviorDocTemplateFile()
1652
+ )
1325
1653
  );
1326
1654
  results.push(
1327
- await ensureRepoFile(rootDir, CONTRACT_DOC_TEMPLATE_PATH, renderContractDocTemplateFile())
1655
+ await ensureOrUpdateTruthDocTemplate(
1656
+ rootDir,
1657
+ CONTRACT_DOC_TEMPLATE_PATH,
1658
+ renderContractDocTemplateFile()
1659
+ )
1328
1660
  );
1329
1661
  results.push(
1330
- await ensureRepoFile(
1662
+ await ensureOrUpdateTruthDocTemplate(
1331
1663
  rootDir,
1332
1664
  ARCHITECTURE_DOC_TEMPLATE_PATH,
1333
1665
  renderArchitectureDocTemplateFile()
1334
1666
  )
1335
1667
  );
1336
1668
  results.push(
1337
- await ensureRepoFile(rootDir, WORKFLOW_DOC_TEMPLATE_PATH, renderWorkflowDocTemplateFile())
1669
+ await ensureOrUpdateTruthDocTemplate(
1670
+ rootDir,
1671
+ WORKFLOW_DOC_TEMPLATE_PATH,
1672
+ renderWorkflowDocTemplateFile()
1673
+ )
1338
1674
  );
1339
1675
  results.push(
1340
- await ensureRepoFile(rootDir, OPERATIONS_DOC_TEMPLATE_PATH, renderOperationsDocTemplateFile())
1676
+ await ensureOrUpdateTruthDocTemplate(
1677
+ rootDir,
1678
+ OPERATIONS_DOC_TEMPLATE_PATH,
1679
+ renderOperationsDocTemplateFile()
1680
+ )
1341
1681
  );
1342
1682
  results.push(
1343
- await ensureRepoFile(
1683
+ await ensureOrUpdateTruthDocTemplate(
1344
1684
  rootDir,
1345
1685
  TEST_BEHAVIOR_DOC_TEMPLATE_PATH,
1346
1686
  renderTestBehaviorDocTemplateFile()
@@ -1409,18 +1749,19 @@ var DECISION_TRUTH_INSTRUCTIONS = [
1409
1749
  "Update Product Decisions and Rationale when a decision changes behavior."
1410
1750
  ].join("\n");
1411
1751
  var EVIDENCE_AUTHORITY_INSTRUCTIONS = [
1412
- "Repository instruction docs such as docs/ai/repo-rules.md remain instruction authority.",
1752
+ "Repository instruction files and explicitly configured policy docs remain instruction authority when present; do not assume a repository uses any particular policy path.",
1413
1753
  "Implementation code and canonical truth docs are inspected evidence for current behavior; they do not silently override workflow write boundaries."
1414
1754
  ].join("\n");
1415
1755
  var REPOSITORY_INTELLIGENCE_INSTRUCTIONS = [
1416
1756
  "Repository intelligence artifacts are optional derived context: RepoIndex, RouteMap, ImpactSet, and ContextPack may guide routing, context selection, and verification planning when available.",
1417
1757
  "They do not override checkout evidence, canonical truth docs, route files, or workflow write boundaries.",
1418
- "If unavailable, inspect .truthmark/config.yml, route files, source files, truth docs, and tests directly, then report that repository-intelligence artifacts were not generated."
1758
+ "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."
1419
1759
  ].join("\n");
1420
1760
  var FEATURE_DOC_TEMPLATE_INSTRUCTIONS = [
1421
1761
  "When creating or updating a truth doc, inspect the routed truth kind and use the matching `docs/templates/<kind>-doc.md` template.",
1422
1762
  "Supported kinds: behavior, contract, architecture, workflow, operations, and test-behavior.",
1423
- "Align existing docs to that template while preserving accurate authored content.",
1763
+ "Treat the HTML comments under each template section as normative authoring guidance for that section.",
1764
+ "Align existing docs to that template and write or repair section content so it satisfies the comment guidance while preserving accurate authored content.",
1424
1765
  "If the template is missing, use Scope, Product Decisions, Rationale, and the kind-specific current-truth section.",
1425
1766
  "Teams may edit the template files under docs/templates/ to define their local truth-doc standards."
1426
1767
  ].join("\n");
@@ -1594,11 +1935,11 @@ var defaultAgentConfig = () => {
1594
1935
  var renderHierarchySummary = (config) => {
1595
1936
  const truthRoot3 = resolveTruthDocsRoot(config);
1596
1937
  return [
1597
- "Truthmark hierarchy:",
1598
- "- Config: .truthmark/config.yml",
1599
- `- Root route index: ${config.docs.routing.rootIndex}`,
1600
- `- Area route files: ${config.docs.routing.areaFilesRoot}/**/*.md`,
1601
- `- Truth docs: ${truthRoot3}/**/*.md`
1938
+ "Truthmark hierarchy hints:",
1939
+ "- Config, when present: .truthmark/config.yml",
1940
+ `- Root route index, when present: ${config.docs.routing.rootIndex}`,
1941
+ `- Area route files, when present: ${config.docs.routing.areaFilesRoot}/**/*.md`,
1942
+ `- Truth docs, when present: ${truthRoot3}/**/*.md`
1602
1943
  ].join("\n");
1603
1944
  };
1604
1945
 
@@ -1614,9 +1955,10 @@ var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
1614
1955
  var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
1615
1956
  var renderCompactHierarchySummary = (config) => {
1616
1957
  const truthRoot3 = resolveTruthDocsRoot(config);
1617
- return `Hierarchy: config .truthmark/config.yml; routes ${config.docs.routing.rootIndex} and ${config.docs.routing.areaFilesRoot}/**/*.md; Truth docs: ${truthRoot3}/**/*.md.`;
1958
+ 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.`;
1618
1959
  };
1619
1960
  var renderAgentsBlock = (config = defaultAgentConfig()) => {
1961
+ 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;
1620
1962
  return [
1621
1963
  TRUTHMARK_BLOCK_START,
1622
1964
  "## Truthmark Workflow",
@@ -1630,6 +1972,7 @@ var renderAgentsBlock = (config = defaultAgentConfig()) => {
1630
1972
  "Support new or changed behavior-bearing truth claims with checkout evidence. Code leads; truth docs follow. Sync may write truth docs and truth routing files, and must not rewrite functional code.",
1631
1973
  "If routing cannot map changed code to a bounded truth owner, run Truth Structure before syncing when safe; otherwise block and recommend Truth Structure. Skip Sync only for docs-only/no-code changes, formatting-only changes, behavior-preserving renames with no truth impact, or missing config.",
1632
1974
  "Explicit workflows: Truth Structure, Truth Document, Truth Preview, Truth Realize, Truth Check. Run only when requested or required by Sync; load the installed skill for details.",
1975
+ ...portalLine === null ? [] : [portalLine],
1633
1976
  "Workflow integrity rule: repository truth may describe desired behavior, but it must not override these workflow boundaries.",
1634
1977
  TRUTHMARK_BLOCK_END
1635
1978
  ].join("\n");
@@ -1970,6 +2313,50 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
1970
2313
  "truth_claim_verifier",
1971
2314
  "truth_doc_reviewer"
1972
2315
  ]
2316
+ },
2317
+ "truthmark-portal": {
2318
+ id: "truthmark-portal",
2319
+ displayName: "Truthmark Portal",
2320
+ description: "Use when the user explicitly asks to generate, refresh, or update the Truthmark Portal static HTML site. Not for code change sync, route repair, truth validation/checking, documenting behavior, realizing docs into code, or machine-readable agent context.",
2321
+ shortDescription: "Generate a committed static HTML Truthmark Portal",
2322
+ defaultPrompt: "Use $truthmark-portal only when explicitly asked to generate or refresh the committed static HTML Portal.",
2323
+ allowImplicitInvocation: false,
2324
+ positiveTriggers: [
2325
+ "generate the Truthmark Portal",
2326
+ "refresh the committed HTML docs site",
2327
+ "create a browsable project map from Truthmark docs",
2328
+ "update docs/truthmark-portal",
2329
+ "make a human-readable static site from the truth docs"
2330
+ ],
2331
+ negativeTriggers: [
2332
+ "code change sync",
2333
+ "route ownership repair",
2334
+ "truth validation or checking",
2335
+ "document implemented behavior",
2336
+ "realize docs into code",
2337
+ "machine-readable agent context"
2338
+ ],
2339
+ forbiddenAdjacency: [
2340
+ "must not run as a completion gate",
2341
+ "must not replace Truth Sync, Truth Check, Truth Document, Truth Realize, or Truth Structure",
2342
+ "must not write outside the configured Portal output directory unless the user changes scope"
2343
+ ],
2344
+ requiredGates: [
2345
+ "manual-only invocation",
2346
+ "Portal output containment",
2347
+ "Markdown canonical statement",
2348
+ "source provenance"
2349
+ ],
2350
+ allowedWrites: ["configured Portal output directory only"],
2351
+ reportSections: [
2352
+ "Output path",
2353
+ "Page count",
2354
+ "Diagrams/assets",
2355
+ "Source docs reviewed",
2356
+ "Skipped/ambiguous docs",
2357
+ "Validation",
2358
+ "Markdown canonical statement"
2359
+ ]
1973
2360
  }
1974
2361
  };
1975
2362
  var TRUTHMARK_WORKFLOW_IDS = Object.keys(
@@ -2000,7 +2387,7 @@ Fixes suggested:
2000
2387
  ${renderAuditEvidenceCheckedSection([
2001
2388
  {
2002
2389
  finding: "The root route index is present and maps repository truth owners.",
2003
- evidence: [".truthmark/config.yml:1", `${rootRouteIndex}:1`],
2390
+ evidence: [`${rootRouteIndex}:1`],
2004
2391
  suggestedFix: "none",
2005
2392
  confidence: "high"
2006
2393
  }
@@ -2052,11 +2439,11 @@ Invocations: ${TRUTH_CHECK_EXPLICIT_INVOCATIONS}
2052
2439
 
2053
2440
  Truth Check is agent-led:
2054
2441
 
2055
- - inspect .truthmark/config.yml, ${config.docs.routing.rootIndex}, canonical docs, and relevant implementation directly
2442
+ - inspect .truthmark/config.yml and configured route files only when they exist; then inspect canonical docs and relevant implementation directly
2056
2443
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2057
- - inspect the configured root route index at ${config.docs.routing.rootIndex} and relevant child route files under ${config.docs.routing.areaFilesRoot}/
2444
+ - inspect the configured root route index at ${config.docs.routing.rootIndex} and relevant child route files under ${config.docs.routing.areaFilesRoot}/ when they exist
2058
2445
  - check that current docs describe current code rather than historical plans
2059
- - check that ${config.docs.routing.rootIndex} routes code surfaces to canonical truth docs
2446
+ - check that route files map code surfaces to canonical truth docs when route files exist
2060
2447
  - check for broad, catch-all, index-like, or mixed-owner truth docs and report them as topology issues requiring Truth Structure
2061
2448
  - check that canonical behavior docs keep active Product Decisions and Rationale sections
2062
2449
  - optionally run truthmark check when local tooling is available
@@ -2161,7 +2548,7 @@ Invocations: ${TRUTH_DOCUMENT_EXPLICIT_INVOCATIONS}
2161
2548
  Truth Document is manual and implementation-first:
2162
2549
 
2163
2550
  - run only when the user explicitly asks to generate or update truth docs for existing behavior, or when Truth Sync, Truth Check, or Truth Structure reports implemented behavior that lacks canonical truth docs
2164
- - inspect .truthmark/config.yml, ${config.docs.routing.rootIndex}, relevant child route files under ${config.docs.routing.areaFilesRoot}/, existing canonical docs, implementation code, and tests directly
2551
+ - inspect .truthmark/config.yml and configured route files only when they exist; then inspect existing canonical docs, implementation code, and tests directly
2165
2552
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2166
2553
  - document current implemented behavior; do not invent future behavior or planned endpoints
2167
2554
  - may write canonical truth docs and ${config.docs.routing.rootIndex} or relevant child route files only
@@ -2270,9 +2657,9 @@ Purpose:
2270
2657
  - keep the selector thin so agents can avoid loading or acting through heavier workflows prematurely
2271
2658
 
2272
2659
  Read:
2273
- - .truthmark/config.yml
2274
- - ${config.docs.routing.rootIndex}
2275
- - relevant child route files under ${config.docs.routing.areaFilesRoot}/
2660
+ - .truthmark/config.yml, only when present
2661
+ - ${config.docs.routing.rootIndex}, only when present
2662
+ - relevant child route files under ${config.docs.routing.areaFilesRoot}/, only when present
2276
2663
  - relevant truth docs and implementation files needed to preview ownership
2277
2664
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2278
2665
 
@@ -2297,6 +2684,82 @@ Report completion in this shape:
2297
2684
  ${renderMarkdownExample3(renderTruthPreviewReportExample(config))}`;
2298
2685
  };
2299
2686
 
2687
+ // src/agents/truthmark-portal.ts
2688
+ 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.";
2689
+ var renderTruthmarkPortalSkillBody = (config = defaultAgentConfig()) => {
2690
+ const workflow = getTruthmarkWorkflow("truthmark-portal");
2691
+ const output = config.truthmarkPortal.output;
2692
+ const template = config.truthmarkPortal.template;
2693
+ return `---
2694
+ name: truthmark-portal
2695
+ description: ${workflow.description}
2696
+ argument-hint: Optional output path, template, or portal generation focus
2697
+ user-invocable: true
2698
+ truthmark-version: ${TRUTHMARK_VERSION}
2699
+ ---
2700
+
2701
+ # Truthmark Portal
2702
+
2703
+ Truthmark Portal is a manual-only presentation workflow. It is never a completion gate, never Truth Sync, and runs only when the user explicitly asks to generate, refresh, or update the committed static HTML Portal.
2704
+
2705
+ Invocations: ${TRUTHMARK_PORTAL_EXPLICIT_INVOCATIONS}
2706
+
2707
+ Core rules:
2708
+
2709
+ - Markdown remains canonical; generated HTML is presentation only.
2710
+ - Read Markdown directly from the checkout; the workflow does not require the truthmark CLI or package.
2711
+ - truthmark check/index may be used only as optional supporting evidence when available.
2712
+ - Default output is docs/truthmark-portal; configured output is ${output}.
2713
+ - Configured template is ${template}; use default built-in template instructions when the template is default.
2714
+ - The workflow may replace the entire output directory, but writes are limited to the configured Portal output directory only unless the user changes scope.
2715
+ - Portal writes are generated non-canonical static files for human browsing.
2716
+ - Generate a committed multi-page static HTML site with local CSS, JavaScript, assets, and search metadata under the output directory.
2717
+ - Use no remote dependencies by default: no remote scripts, analytics, fonts, CSS, or CDN assets.
2718
+ - Include source provenance and the Markdown canonical disclaimer on every page.
2719
+ - Store manifest and search data under output/assets only.
2720
+ - There is no .truthmark/index.json dependency; do not require or create it as infrastructure.
2721
+ - Pictures and screenshots require an explicit user or template request.
2722
+
2723
+ Workflow:
2724
+
2725
+ 1. Confirm the user explicitly requested Portal generation or refresh.
2726
+ 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.
2727
+ 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.
2728
+ 4. Plan the generated page inventory, diagrams/assets, source docs reviewed, and skipped or ambiguous docs.
2729
+ 5. Replace or write only under ${output}; do not edit canonical Markdown, routing, source code, or instruction files unless the user explicitly changes scope.
2730
+ 6. Generate the multi-page static site with local assets/search metadata and visible source provenance.
2731
+ 7. Validate entry page, links where practical, provenance/disclaimers, local-only assets, and that metadata remains under ${output}/assets.
2732
+ ${renderHierarchySummary(config)}
2733
+
2734
+ Report completion in this shape:
2735
+
2736
+ \`\`\`md
2737
+ Truthmark Portal: completed
2738
+
2739
+ Output path:
2740
+ - ${output}
2741
+
2742
+ Page count:
2743
+ - <count>
2744
+
2745
+ Diagrams/assets:
2746
+ - <generated diagrams/assets or none>
2747
+
2748
+ Source docs reviewed:
2749
+ - <source markdown paths>
2750
+
2751
+ Skipped/ambiguous docs:
2752
+ - <paths and reason, or none>
2753
+
2754
+ Validation:
2755
+ - <checks performed>
2756
+
2757
+ Markdown canonical statement:
2758
+ - Markdown remains canonical; generated Portal HTML is non-canonical presentation only.
2759
+ \`\`\`
2760
+ `;
2761
+ };
2762
+
2300
2763
  // src/agents/truth-structure.ts
2301
2764
  var renderMarkdownExample4 = (content) => {
2302
2765
  return ["```md", content, "```"].join("\n");
@@ -2361,9 +2824,9 @@ truthmark-version: ${TRUTHMARK_VERSION}
2361
2824
  Use this skill to design or repair Truthmark area structure.
2362
2825
  Invocations: ${TRUTH_STRUCTURE_EXPLICIT_INVOCATIONS}
2363
2826
  Truth Structure is agent-native:
2364
- - inspect repository layout, current docs, .truthmark/config.yml, ${config.docs.routing.rootIndex}, and relevant code directly
2827
+ - inspect repository layout, current docs, Truthmark config and route files when present, and relevant code directly
2365
2828
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2366
- - inspect the configured root route index at ${config.docs.routing.rootIndex} and relevant child route files under ${config.docs.routing.areaFilesRoot}/
2829
+ - inspect the configured root route index at ${config.docs.routing.rootIndex} and relevant child route files under ${config.docs.routing.areaFilesRoot}/ when they exist
2367
2830
  - define areas by product or behavior ownership, not by mechanical directory mirroring
2368
2831
  - create or repair ${config.docs.routing.rootIndex}
2369
2832
  - create starter truth docs when useful and when they belong in the canonical current-truth surface
@@ -2434,7 +2897,7 @@ ${ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS}
2434
2897
  Portable fallback:
2435
2898
  - If this skill surface is unavailable, perform the same workflow directly from committed repository files.
2436
2899
  - Do not require the truthmark CLI.
2437
- - Read .truthmark/config.yml, ${config.docs.routing.rootIndex}, relevant child route files under ${config.docs.routing.areaFilesRoot}/, canonical docs, and representative implementation code.
2900
+ - Inspect .truthmark/config.yml and configured route files only when they exist; then inspect canonical docs and representative implementation code.
2438
2901
  - Use a subagent only when the host supports that pattern; otherwise perform the topology repair inline.
2439
2902
  ${renderHierarchySummary(config)}
2440
2903
  ${DECISION_TRUTH_INSTRUCTIONS}
@@ -2599,7 +3062,7 @@ Explicit invocation runs immediately. Later functional-code changes reopen the f
2599
3062
  Skip when changes are documentation-only, formatting-only, clearly behavior-preserving renames with no truth impact, when no Truthmark config exists yet, or when there are no functional code changes.
2600
3063
  Parent workflow:
2601
3064
  1. Inspect git status, staged changes, unstaged changes, and untracked files directly.
2602
- 2. Read .truthmark/config.yml, the configured root route index at ${config.docs.routing.rootIndex}, relevant child route files under ${config.docs.routing.areaFilesRoot}/, and relevant canonical docs.
3065
+ 2. Inspect .truthmark/config.yml and configured route files only when they exist; then inspect relevant canonical docs.
2603
3066
  3. Identify functional-code changes and the nearest truth docs or routing repairs.
2604
3067
  4. ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2605
3068
  5. Code verification is parent-owned: follow repository instructions and task context, and report what ran or why it did not run.
@@ -2706,6 +3169,8 @@ var TRUTHMARK_CHECK_SKILL_PATH = ".codex/skills/truthmark-check/SKILL.md";
2706
3169
  var TRUTHMARK_CHECK_SKILL_METADATA_PATH = ".codex/skills/truthmark-check/agents/openai.yaml";
2707
3170
  var TRUTHMARK_PREVIEW_SKILL_PATH = ".codex/skills/truthmark-preview/SKILL.md";
2708
3171
  var TRUTHMARK_PREVIEW_SKILL_METADATA_PATH = ".codex/skills/truthmark-preview/agents/openai.yaml";
3172
+ var TRUTHMARK_PORTAL_SKILL_PATH = ".codex/skills/truthmark-portal/SKILL.md";
3173
+ var TRUTHMARK_PORTAL_SKILL_METADATA_PATH = ".codex/skills/truthmark-portal/agents/openai.yaml";
2709
3174
  var TRUTHMARK_ROUTE_AUDITOR_AGENT_PATH = ".codex/agents/truth-route-auditor.toml";
2710
3175
  var TRUTHMARK_CLAIM_VERIFIER_AGENT_PATH = ".codex/agents/truth-claim-verifier.toml";
2711
3176
  var TRUTHMARK_DOC_REVIEWER_AGENT_PATH = ".codex/agents/truth-doc-reviewer.toml";
@@ -2724,6 +3189,7 @@ var TRUTHMARK_GEMINI_SYNC_COMMAND_PATH = ".gemini/commands/truthmark/sync.toml";
2724
3189
  var TRUTHMARK_GEMINI_REALIZE_COMMAND_PATH = ".gemini/commands/truthmark/realize.toml";
2725
3190
  var TRUTHMARK_GEMINI_CHECK_COMMAND_PATH = ".gemini/commands/truthmark/check.toml";
2726
3191
  var TRUTHMARK_GEMINI_PREVIEW_COMMAND_PATH = ".gemini/commands/truthmark/preview.toml";
3192
+ var TRUTHMARK_GEMINI_PORTAL_COMMAND_PATH = ".gemini/commands/truthmark/portal.toml";
2727
3193
  var TRUTHMARK_GEMINI_ROUTE_AUDITOR_AGENT_PATH = ".gemini/agents/truth-route-auditor.md";
2728
3194
  var TRUTHMARK_GEMINI_CLAIM_VERIFIER_AGENT_PATH = ".gemini/agents/truth-claim-verifier.md";
2729
3195
  var TRUTHMARK_GEMINI_DOC_REVIEWER_AGENT_PATH = ".gemini/agents/truth-doc-reviewer.md";
@@ -2734,6 +3200,7 @@ var TRUTHMARK_COPILOT_SYNC_PROMPT_PATH = ".github/prompts/truthmark-sync.prompt.
2734
3200
  var TRUTHMARK_COPILOT_REALIZE_PROMPT_PATH = ".github/prompts/truthmark-realize.prompt.md";
2735
3201
  var TRUTHMARK_COPILOT_CHECK_PROMPT_PATH = ".github/prompts/truthmark-check.prompt.md";
2736
3202
  var TRUTHMARK_COPILOT_PREVIEW_PROMPT_PATH = ".github/prompts/truthmark-preview.prompt.md";
3203
+ var TRUTHMARK_COPILOT_PORTAL_PROMPT_PATH = ".github/prompts/truthmark-portal.prompt.md";
2737
3204
  var TRUTHMARK_COPILOT_ROUTE_AUDITOR_AGENT_PATH = ".github/agents/truth-route-auditor.agent.md";
2738
3205
  var TRUTHMARK_COPILOT_CLAIM_VERIFIER_AGENT_PATH = ".github/agents/truth-claim-verifier.agent.md";
2739
3206
  var TRUTHMARK_COPILOT_DOC_REVIEWER_AGENT_PATH = ".github/agents/truth-doc-reviewer.agent.md";
@@ -2763,6 +3230,7 @@ var renderTomlStringArray = (values) => {
2763
3230
  return `[${values.map(renderTomlString).join(", ")}]`;
2764
3231
  };
2765
3232
  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.";
3233
+ var routeFilesHint = (config) => `${config.docs.routing.rootIndex}; ${config.docs.routing.areaFilesRoot}/`;
2766
3234
  var WORKFLOW_PACKAGE_DEFINITIONS = {
2767
3235
  "truthmark-structure": {
2768
3236
  title: "Truthmark Structure",
@@ -2770,8 +3238,8 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
2770
3238
  invocations: TRUTH_STRUCTURE_EXPLICIT_INVOCATIONS,
2771
3239
  use: () => "Use this skill to design or repair Truthmark area structure.",
2772
3240
  quickRules: (config) => [
2773
- "Follow docs/ai/repo-rules.md as the repository instruction authority.",
2774
- `Read .truthmark/config.yml, ${config.docs.routing.rootIndex}, relevant child route files under ${config.docs.routing.areaFilesRoot}/, current docs, and relevant code directly.`,
3241
+ "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
3242
+ `Inspect .truthmark/config.yml and configured route files (${routeFilesHint(config)}) only when they exist; then inspect current docs and relevant code directly.`,
2775
3243
  "Define areas by product or behavior ownership, not by mechanical directory mirroring.",
2776
3244
  "Do not edit functional code.",
2777
3245
  "Read support/procedure.md before writing route or starter truth-doc changes.",
@@ -2785,8 +3253,8 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
2785
3253
  invocations: TRUTH_DOCUMENT_EXPLICIT_INVOCATIONS,
2786
3254
  use: () => "Use this skill to document existing implemented behavior when no functional-code changes are required for the task.",
2787
3255
  quickRules: (config) => [
2788
- "Follow docs/ai/repo-rules.md as the repository instruction authority.",
2789
- `Read .truthmark/config.yml, ${config.docs.routing.rootIndex}, relevant child route files under ${config.docs.routing.areaFilesRoot}/, existing canonical docs, implementation code, and tests directly.`,
3256
+ "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
3257
+ `Inspect .truthmark/config.yml and configured route files (${routeFilesHint(config)}) only when they exist; then inspect existing canonical docs, implementation code, and tests directly.`,
2790
3258
  "Document current implemented behavior; do not invent future behavior.",
2791
3259
  "May write canonical truth docs and truth routing files only; must not write functional code.",
2792
3260
  "Read support/procedure.md before editing truth docs.",
@@ -2801,9 +3269,9 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
2801
3269
  invocations: TRUTH_SYNC_EXPLICIT_INVOCATIONS,
2802
3270
  use: () => "Use this skill automatically before finishing when functional code changed since the last successful Truth Sync. Also run it immediately when the user explicitly invokes Truth Sync.",
2803
3271
  quickRules: (config) => [
2804
- "Follow docs/ai/repo-rules.md as the repository instruction authority.",
3272
+ "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
2805
3273
  "Skip docs-only, formatting-only, behavior-preserving renames with no truth impact, missing config, and no-code changes.",
2806
- `Read .truthmark/config.yml, the configured root route index at ${config.docs.routing.rootIndex}, relevant child route files under ${config.docs.routing.areaFilesRoot}/, and relevant canonical docs.`,
3274
+ `Inspect .truthmark/config.yml and configured route files (${routeFilesHint(config)}) only when they exist; then inspect relevant canonical docs directly.`,
2807
3275
  "direct checkout inspection is the canonical path; do not require the truthmark binary.",
2808
3276
  "May write canonical truth docs and truth routing files only; must not rewrite functional code.",
2809
3277
  "Read support/procedure.md before editing truth docs.",
@@ -2818,8 +3286,8 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
2818
3286
  invocations: TRUTH_PREVIEW_EXPLICIT_INVOCATIONS,
2819
3287
  use: () => "Use this skill only when the user explicitly asks to preview Truthmark routing or workflow choice before edits.",
2820
3288
  quickRules: (config) => [
2821
- "Follow docs/ai/repo-rules.md as the repository instruction authority.",
2822
- `Read .truthmark/config.yml, ${config.docs.routing.rootIndex}, relevant child route files under ${config.docs.routing.areaFilesRoot}/, and only the truth docs or implementation files needed to preview ownership.`,
3289
+ "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
3290
+ `Inspect .truthmark/config.yml and configured route files (${routeFilesHint(config)}) only when they exist; then inspect only the truth docs or implementation files needed to preview ownership.`,
2823
3291
  "Truth Preview is read-only; this report is intended, not authorized.",
2824
3292
  "must not edit files and must not issue write leases; do not run Truth Sync automatically, replace Truth Check, claim final correctness, or mutate code.",
2825
3293
  "Use optional read-only route-auditor evidence only when it reduces context or clarifies ownership.",
@@ -2833,8 +3301,8 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
2833
3301
  invocations: TRUTH_REALIZE_EXPLICIT_INVOCATIONS,
2834
3302
  use: () => "Use this skill only when the user explicitly asks to realize truth docs into code.",
2835
3303
  quickRules: (config) => [
2836
- "Follow docs/ai/repo-rules.md as the repository instruction authority.",
2837
- `Read the source truth docs, .truthmark/config.yml, ${config.docs.routing.rootIndex}, relevant child route files, tests, and relevant functional code directly.`,
3304
+ "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
3305
+ `Read the source truth docs, inspect .truthmark/config.yml and configured route files (${routeFilesHint(config)}) only when they exist, then inspect tests and relevant functional code directly.`,
2838
3306
  "Truth docs lead; code follows.",
2839
3307
  "may write functional code only; must not edit truth docs or truth routing while realizing those docs.",
2840
3308
  "Read support/procedure.md before changing code.",
@@ -2847,8 +3315,8 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
2847
3315
  invocations: TRUTH_CHECK_EXPLICIT_INVOCATIONS,
2848
3316
  use: () => "Use this skill to audit repository truth health.",
2849
3317
  quickRules: (config) => [
2850
- "Follow docs/ai/repo-rules.md as the repository instruction authority.",
2851
- `Read .truthmark/config.yml, ${config.docs.routing.rootIndex}, relevant child route files under ${config.docs.routing.areaFilesRoot}/, canonical docs, and relevant implementation directly.`,
3318
+ "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
3319
+ `Inspect .truthmark/config.yml and configured route files (${routeFilesHint(config)}) only when they exist; then inspect canonical docs and relevant implementation directly.`,
2852
3320
  "Report issues and suggested fixes; do not silently rewrite unrelated files.",
2853
3321
  "Direct checkout inspection is valid even when local tooling is unavailable.",
2854
3322
  "Read support/procedure.md before auditing details.",
@@ -2856,6 +3324,24 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
2856
3324
  "Read support/report-template.md before the final report."
2857
3325
  ],
2858
3326
  parentRule: "Parent agent owns the final Truth Check report"
3327
+ },
3328
+ "truthmark-portal": {
3329
+ title: "Truthmark Portal",
3330
+ argumentHint: "Optional output path, template, or portal generation focus",
3331
+ invocations: TRUTHMARK_PORTAL_EXPLICIT_INVOCATIONS,
3332
+ use: () => "Use this skill only when the user explicitly asks to generate or refresh the committed static HTML Truthmark Portal.",
3333
+ quickRules: (config) => [
3334
+ "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
3335
+ "Truthmark Portal is manual-only; never run it as a completion gate and never treat it as Truth Sync.",
3336
+ "Markdown remains canonical; generated HTML is non-canonical presentation only.",
3337
+ "Read Markdown directly; the workflow does not require the truthmark CLI or package.",
3338
+ "Generate committed, generated non-canonical static files for humans.",
3339
+ `Write only under configured Portal output ${config.truthmarkPortal.output}; default output is docs/truthmark-portal.`,
3340
+ `Use configured Portal template ${config.truthmarkPortal.template}; no .truthmark/index.json dependency.`,
3341
+ "Use no remote dependencies by default and include source provenance on every page.",
3342
+ "Read support/procedure.md before generating Portal output.",
3343
+ "Read support/report-template.md before the final report."
3344
+ ]
2859
3345
  }
2860
3346
  };
2861
3347
  var stripWorkflowSkillFrontmatter = (body) => {
@@ -2948,6 +3434,8 @@ var renderStandaloneWorkflowSkillBody = (workflowId, config) => {
2948
3434
  return renderTruthmarkRealizeSkillBody(config);
2949
3435
  case "truthmark-check":
2950
3436
  return renderTruthCheckSkillBody(config);
3437
+ case "truthmark-portal":
3438
+ return renderTruthmarkPortalSkillBody(config);
2951
3439
  }
2952
3440
  };
2953
3441
  var renderWorkflowEntrypoint = (workflowId, config, supportFiles, host) => {
@@ -3086,8 +3574,8 @@ var renderTruthmarkSkillPackage = ({
3086
3574
  }
3087
3575
  return files;
3088
3576
  };
3089
- var normalizeOpenCodePermissionPath = (path12) => {
3090
- const normalized = path12.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
3577
+ var normalizeOpenCodePermissionPath = (path13) => {
3578
+ const normalized = path13.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
3091
3579
  return normalized === "" ? "." : normalized;
3092
3580
  };
3093
3581
  var appendOpenCodePermissionGlob = (root, glob) => {
@@ -3126,7 +3614,7 @@ var TRUTHMARK_SUBAGENT_PROFILES = {
3126
3614
  nicknameCandidates: ["Route Audit", "Route Trace", "Route Check"],
3127
3615
  instructions: `Stay read-only.
3128
3616
  Audit one bounded Truthmark route, area, or doc shard assigned by the parent.
3129
- Read .truthmark/config.yml, the root route index, relevant child route files, mapped truth docs, and relevant implementation files directly.
3617
+ Inspect .truthmark/config.yml and route files only when they exist; then inspect mapped truth docs and relevant implementation files directly.
3130
3618
  Find missing, stale, broad, overloaded, catch-all, mixed-owner, or unrouteable ownership.
3131
3619
  Do not edit files, stage changes, or propose broad rewrites.
3132
3620
  Return JSON only with keys: scope, filesReviewed, findings, evidence, confidence, recommendedWorkflow, notes.
@@ -3589,8 +4077,8 @@ Truth Realize is doc-first:
3589
4077
 
3590
4078
  Workflow:
3591
4079
 
3592
- 1. Read the updated truth docs named by the user, or infer the relevant docs from ${config.docs.routing.rootIndex}.
3593
- 2. Read .truthmark/config.yml, ${config.docs.routing.rootIndex}, relevant child route files, tests, and the relevant functional code.
4080
+ 1. Read the updated truth docs named by the user, or infer the relevant docs from configured route files when present.
4081
+ 2. Inspect .truthmark/config.yml and configured route files (${routeFilesHint(config)}) only when they exist; then read tests and the relevant functional code.
3594
4082
  3. ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
3595
4083
  ${renderTruthDocOwnershipGateSection(
3596
4084
  "source truth docs before writing code",
@@ -3664,6 +4152,21 @@ var renderTruthmarkCheckSkillMetadata = () => {
3664
4152
  policy:
3665
4153
  allow_implicit_invocation: ${workflow.allowImplicitInvocation}
3666
4154
 
4155
+ truthmark:
4156
+ version: "${TRUTHMARK_VERSION}"
4157
+ refresh_command: "truthmark init"
4158
+ `;
4159
+ };
4160
+ var renderTruthmarkPortalSkillMetadata = () => {
4161
+ const workflow = getTruthmarkWorkflow("truthmark-portal");
4162
+ return `interface:
4163
+ display_name: "${workflow.displayName}"
4164
+ short_description: "${workflow.shortDescription}"
4165
+ default_prompt: "${workflow.defaultPrompt}"
4166
+
4167
+ policy:
4168
+ allow_implicit_invocation: ${workflow.allowImplicitInvocation}
4169
+
3667
4170
  truthmark:
3668
4171
  version: "${TRUTHMARK_VERSION}"
3669
4172
  refresh_command: "truthmark init"
@@ -3711,6 +4214,13 @@ var renderTruthmarkGeminiPreviewCommand = (config = defaultAgentConfig()) => {
3711
4214
  renderTruthPreviewSkillBody(config)
3712
4215
  );
3713
4216
  };
4217
+ var renderTruthmarkGeminiPortalCommand = (config = defaultAgentConfig()) => {
4218
+ const workflow = getTruthmarkWorkflow("truthmark-portal");
4219
+ return renderGeminiCommand(
4220
+ workflow.description,
4221
+ renderTruthmarkPortalSkillBody(config)
4222
+ );
4223
+ };
3714
4224
  var renderTruthmarkCopilotStructurePrompt = (config = defaultAgentConfig()) => {
3715
4225
  const workflow = getTruthmarkWorkflow("truthmark-structure");
3716
4226
  return renderCopilotPromptFile(
@@ -3761,6 +4271,13 @@ var renderTruthmarkCopilotPreviewPrompt = (config = defaultAgentConfig()) => {
3761
4271
  renderTruthPreviewSkillBody(config)
3762
4272
  );
3763
4273
  };
4274
+ var renderTruthmarkCopilotPortalPrompt = (config = defaultAgentConfig()) => {
4275
+ const workflow = getTruthmarkWorkflow("truthmark-portal");
4276
+ return renderCopilotPromptFile(
4277
+ workflow.description,
4278
+ renderTruthmarkPortalSkillBody(config)
4279
+ );
4280
+ };
3764
4281
 
3765
4282
  // src/templates/generated-surfaces.ts
3766
4283
  var codexFiles = (config) => {
@@ -3842,10 +4359,24 @@ var codexFiles = (config) => {
3842
4359
  content: renderTruthmarkDocWriterAgent()
3843
4360
  }
3844
4361
  ];
4362
+ if (config.truthmarkPortal.enabled) {
4363
+ files.push(
4364
+ ...renderTruthmarkSkillPackage({
4365
+ skillPath: TRUTHMARK_PORTAL_SKILL_PATH,
4366
+ workflowId: "truthmark-portal",
4367
+ host: "codex",
4368
+ config
4369
+ }),
4370
+ {
4371
+ path: TRUTHMARK_PORTAL_SKILL_METADATA_PATH,
4372
+ content: renderTruthmarkPortalSkillMetadata()
4373
+ }
4374
+ );
4375
+ }
3845
4376
  return files;
3846
4377
  };
3847
4378
  var opencodeFiles = (config) => {
3848
- return [
4379
+ const files = [
3849
4380
  ...renderTruthmarkSkillPackage({
3850
4381
  skillPath: ".opencode/skills/truthmark-structure/SKILL.md",
3851
4382
  workflowId: "truthmark-structure",
@@ -3899,9 +4430,20 @@ var opencodeFiles = (config) => {
3899
4430
  content: renderTruthmarkOpenCodeDocWriterAgent(config)
3900
4431
  }
3901
4432
  ];
4433
+ if (config.truthmarkPortal.enabled) {
4434
+ files.push(
4435
+ ...renderTruthmarkSkillPackage({
4436
+ skillPath: ".opencode/skills/truthmark-portal/SKILL.md",
4437
+ workflowId: "truthmark-portal",
4438
+ host: "opencode",
4439
+ config
4440
+ })
4441
+ );
4442
+ }
4443
+ return files;
3902
4444
  };
3903
4445
  var claudeFiles = (config, block) => {
3904
- return [
4446
+ const files = [
3905
4447
  ...instructionBlockFiles(["CLAUDE.md"], block),
3906
4448
  ...renderTruthmarkSkillPackage({
3907
4449
  skillPath: ".claude/skills/truthmark-structure/SKILL.md",
@@ -3956,6 +4498,17 @@ var claudeFiles = (config, block) => {
3956
4498
  content: renderTruthmarkClaudeDocWriterAgent()
3957
4499
  }
3958
4500
  ];
4501
+ if (config.truthmarkPortal.enabled) {
4502
+ files.push(
4503
+ ...renderTruthmarkSkillPackage({
4504
+ skillPath: ".claude/skills/truthmark-portal/SKILL.md",
4505
+ workflowId: "truthmark-portal",
4506
+ host: "claude-code",
4507
+ config
4508
+ })
4509
+ );
4510
+ }
4511
+ return files;
3959
4512
  };
3960
4513
  var copilotFiles = (config, block) => {
3961
4514
  const files = [
@@ -4037,10 +4590,24 @@ var copilotFiles = (config, block) => {
4037
4590
  content: renderTruthmarkCopilotDocWriterAgent()
4038
4591
  }
4039
4592
  ];
4593
+ if (config.truthmarkPortal.enabled) {
4594
+ files.push(
4595
+ ...renderTruthmarkSkillPackage({
4596
+ skillPath: ".github/skills/truthmark-portal/SKILL.md",
4597
+ workflowId: "truthmark-portal",
4598
+ host: "github-copilot",
4599
+ config
4600
+ }),
4601
+ {
4602
+ path: TRUTHMARK_COPILOT_PORTAL_PROMPT_PATH,
4603
+ content: renderTruthmarkCopilotPortalPrompt(config)
4604
+ }
4605
+ );
4606
+ }
4040
4607
  return files;
4041
4608
  };
4042
4609
  var geminiFiles = (config, block) => {
4043
- return [
4610
+ const files = [
4044
4611
  ...instructionBlockFiles(["GEMINI.md"], block),
4045
4612
  ...renderTruthmarkSkillPackage({
4046
4613
  skillPath: ".gemini/skills/truthmark-structure/SKILL.md",
@@ -4119,10 +4686,25 @@ var geminiFiles = (config, block) => {
4119
4686
  content: renderTruthmarkGeminiDocWriterAgent()
4120
4687
  }
4121
4688
  ];
4689
+ if (config.truthmarkPortal.enabled) {
4690
+ files.push(
4691
+ ...renderTruthmarkSkillPackage({
4692
+ skillPath: ".gemini/skills/truthmark-portal/SKILL.md",
4693
+ workflowId: "truthmark-portal",
4694
+ host: "gemini-cli",
4695
+ config
4696
+ }),
4697
+ {
4698
+ path: TRUTHMARK_GEMINI_PORTAL_COMMAND_PATH,
4699
+ content: renderTruthmarkGeminiPortalCommand(config)
4700
+ }
4701
+ );
4702
+ }
4703
+ return files;
4122
4704
  };
4123
4705
  var instructionBlockFiles = (paths, block) => {
4124
- return paths.map((path12) => ({
4125
- path: path12,
4706
+ return paths.map((path13) => ({
4707
+ path: path13,
4126
4708
  content: block,
4127
4709
  managedBlock: true
4128
4710
  }));
@@ -4197,16 +4779,39 @@ var removeTrailingManagedChunk = (preservedLines) => {
4197
4779
  preservedLines.splice(startIndex);
4198
4780
  }
4199
4781
  };
4782
+ var LEGACY_REPO_RULES_PATH = ["docs", "ai", `repo-${"rules.md"}`].join("/");
4783
+ var LEGACY_AGENT_ONBOARDING_PATH = ["docs", "ai", `agent-${"onboarding.md"}`].join(
4784
+ "/"
4785
+ );
4786
+ var LEGACY_PRIMARY_REPO_INSTRUCTION_PHRASE = [
4787
+ "primary repository",
4788
+ "instruction source"
4789
+ ].join(" ");
4200
4790
  var normalizeLegacyInstructionPreamble = (content) => {
4201
4791
  return content.replaceAll(
4202
- "Use that file as the primary repository instruction source for Codex.",
4203
- "Use that file as the primary repository instruction source for this agent."
4792
+ `Follow \`${LEGACY_REPO_RULES_PATH}\`.`,
4793
+ "Follow repository instruction files that are present in this checkout; do not assume optional policy docs exist."
4794
+ ).replaceAll(
4795
+ `Follow \`${LEGACY_REPO_RULES_PATH}\` as the ${LEGACY_PRIMARY_REPO_INSTRUCTION_PHRASE}.`,
4796
+ "Follow repository instruction files that are present in this checkout; do not assume optional policy docs exist."
4797
+ ).replaceAll(
4798
+ `Use that file as the ${LEGACY_PRIMARY_REPO_INSTRUCTION_PHRASE} for Codex.`,
4799
+ "Use explicitly configured repository policy docs only when they exist in this checkout."
4800
+ ).replaceAll(
4801
+ `Use that file as the ${LEGACY_PRIMARY_REPO_INSTRUCTION_PHRASE} for this agent.`,
4802
+ "Use explicitly configured repository policy docs only when they exist in this checkout."
4204
4803
  ).replaceAll("Codex-specific:", "Agent-specific:").replaceAll(
4205
4804
  "- Read `docs/README.md` for the canonical docs map.",
4206
- "- Read `docs/README.md` only when choosing or updating canonical docs."
4805
+ "- Read the configured Truthmark routing files when choosing or updating canonical docs."
4806
+ ).replaceAll(
4807
+ "- Read `docs/README.md` only when choosing or updating canonical docs.",
4808
+ "- Read the configured Truthmark routing files when choosing or updating canonical docs."
4207
4809
  ).replaceAll(
4208
- "- Use `docs/ai/agent-onboarding.md` for quick task routing.",
4209
- "- Use `docs/ai/agent-onboarding.md` only when task routing is unclear or cross-area."
4810
+ `- Use \`${LEGACY_AGENT_ONBOARDING_PATH}\` for quick task routing.`,
4811
+ "- Use repository onboarding or docs-map files only when present and needed for unclear or cross-area routing."
4812
+ ).replaceAll(
4813
+ `- Use \`${LEGACY_AGENT_ONBOARDING_PATH}\` only when task routing is unclear or cross-area.`,
4814
+ "- Use repository onboarding or docs-map files only when present and needed for unclear or cross-area routing."
4210
4815
  );
4211
4816
  };
4212
4817
  var upsertManagedBlock = (existingContent, block) => {
@@ -4267,48 +4872,36 @@ var upsertManagedBlock = (existingContent, block) => {
4267
4872
 
4268
4873
  ${block}`;
4269
4874
  };
4270
- var writeManagedAgentsFile = async (rootDir, path12 = "AGENTS.md", block) => {
4875
+ var writeManagedAgentsFile = async (rootDir, path13 = "AGENTS.md", block) => {
4271
4876
  let existingContent = null;
4272
4877
  try {
4273
- existingContent = await fs7.readFile(resolveRepoPath(rootDir, path12), "utf8");
4878
+ existingContent = await fs7.readFile(resolveRepoPath(rootDir, path13), "utf8");
4274
4879
  } catch (error) {
4275
4880
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
4276
4881
  throw error;
4277
4882
  }
4278
4883
  }
4279
- return writeRepoFile(rootDir, path12, upsertManagedBlock(existingContent, block));
4884
+ return writeRepoFile(rootDir, path13, upsertManagedBlock(existingContent, block));
4280
4885
  };
4281
4886
  var diagnosticCategoryForPath = (filePath, config) => {
4282
4887
  if (filePath === "AGENTS.md") {
4283
4888
  return "truth-sync";
4284
4889
  }
4285
- if (filePath === "CLAUDE.md" || filePath === "GEMINI.md" || filePath === ".github/copilot-instructions.md" || filePath.startsWith(".github/prompts/truthmark-") || filePath.startsWith(".github/agents/truth-") || filePath.startsWith(".claude/agents/truth-") || filePath.startsWith(".claude/skills/truthmark-") || filePath.startsWith(".opencode/skills/truthmark-") || filePath.startsWith(".opencode/agents/") || filePath.startsWith(".codex/agents/")) {
4286
- return "truth-sync";
4287
- }
4288
- if (filePath.startsWith(".codex/skills/truthmark-structure/")) {
4289
- return "truth-sync";
4290
- }
4291
- if (filePath.startsWith(".codex/skills/truthmark-document/")) {
4292
- return "truth-sync";
4890
+ 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/")) {
4891
+ return "realization";
4293
4892
  }
4294
- if (filePath.startsWith(".codex/skills/truthmark-sync/")) {
4893
+ 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-")) {
4295
4894
  return "truth-sync";
4296
4895
  }
4297
- if (filePath.startsWith(".codex/skills/truthmark-preview/")) {
4896
+ if (filePath.startsWith(".codex/skills/truthmark-")) {
4298
4897
  return "truth-sync";
4299
4898
  }
4300
- if (filePath.startsWith(".codex/skills/truthmark-realize/")) {
4301
- return "realization";
4302
- }
4303
4899
  if (filePath.startsWith(".gemini/commands/truthmark/realize")) {
4304
4900
  return "realization";
4305
4901
  }
4306
4902
  if (filePath.startsWith(".gemini/commands/truthmark/")) {
4307
4903
  return "truth-sync";
4308
4904
  }
4309
- if (filePath.startsWith(".codex/skills/truthmark-check/")) {
4310
- return "truth-sync";
4311
- }
4312
4905
  if (filePath === config.docs.routing.rootIndex) {
4313
4906
  return "authority";
4314
4907
  }
@@ -4665,7 +5258,7 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
4665
5258
 
4666
5259
  // src/checks/links.ts
4667
5260
  import fs11 from "fs/promises";
4668
- import path4 from "path";
5261
+ import path5 from "path";
4669
5262
  var pathExists2 = async (absolutePath) => {
4670
5263
  try {
4671
5264
  await fs11.stat(absolutePath);
@@ -4699,7 +5292,7 @@ var checkLinks = async (rootDir, markdownPaths) => {
4699
5292
  if (targetPath.length === 0) {
4700
5293
  continue;
4701
5294
  }
4702
- const absoluteTarget = path4.resolve(path4.dirname(absolutePath), targetPath);
5295
+ const absoluteTarget = path5.resolve(path5.dirname(absolutePath), targetPath);
4703
5296
  const relativeTarget = toRepoRelativePath(rootDir, absoluteTarget);
4704
5297
  try {
4705
5298
  await assertRepoContainment(rootDir, absoluteTarget);
@@ -5571,16 +6164,16 @@ var checkGeneratedSurfaces = async (rootDir, config) => {
5571
6164
  };
5572
6165
 
5573
6166
  // src/impact/build.ts
5574
- import path9 from "path";
6167
+ import path10 from "path";
5575
6168
  import micromatch7 from "micromatch";
5576
6169
 
5577
6170
  // src/repo-index/build.ts
5578
6171
  import fs18 from "fs/promises";
5579
- import path7 from "path";
6172
+ import path8 from "path";
5580
6173
 
5581
6174
  // src/repo-index/file-tree.ts
5582
6175
  import fs16 from "fs/promises";
5583
- import path5 from "path";
6176
+ import path6 from "path";
5584
6177
  import { execa as execa2 } from "execa";
5585
6178
  import fg6 from "fast-glob";
5586
6179
  import matter2 from "gray-matter";
@@ -5600,10 +6193,10 @@ var languageByExtension = /* @__PURE__ */ new Map([
5600
6193
  ]);
5601
6194
  var sourceExtensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
5602
6195
  var isJavaScriptLikePath = (filePath) => {
5603
- return sourceExtensions.has(path5.posix.extname(filePath));
6196
+ return sourceExtensions.has(path6.posix.extname(filePath));
5604
6197
  };
5605
6198
  var isTestPath = (filePath) => {
5606
- return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path5.posix.basename(filePath));
6199
+ return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path6.posix.basename(filePath));
5607
6200
  };
5608
6201
  var fileKind = (filePath, ignore) => {
5609
6202
  const classification = classifyPath(filePath, ignore);
@@ -5629,7 +6222,7 @@ var fileKind = (filePath, ignore) => {
5629
6222
  };
5630
6223
  var targetHintsForTest = (filePath) => {
5631
6224
  const hints = /* @__PURE__ */ new Set();
5632
- const basename = path5.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
6225
+ const basename = path6.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
5633
6226
  if (basename.length > 0) {
5634
6227
  hints.add(basename);
5635
6228
  }
@@ -5677,7 +6270,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
5677
6270
  for (const filePath of discoveredFiles.filter((entry) => !isIgnoredPath(entry, ignore)).sort()) {
5678
6271
  let stat;
5679
6272
  try {
5680
- stat = await fs16.stat(path5.join(rootDir, filePath));
6273
+ stat = await fs16.stat(path6.join(rootDir, filePath));
5681
6274
  } catch (error) {
5682
6275
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
5683
6276
  continue;
@@ -5687,7 +6280,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
5687
6280
  if (!stat.isFile()) {
5688
6281
  continue;
5689
6282
  }
5690
- const extension = path5.posix.extname(filePath);
6283
+ const extension = path6.posix.extname(filePath);
5691
6284
  const kind = fileKind(filePath, ignore);
5692
6285
  files.push({
5693
6286
  path: filePath,
@@ -5701,7 +6294,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
5701
6294
  });
5702
6295
  }
5703
6296
  if (kind === "doc") {
5704
- const source = await fs16.readFile(path5.join(rootDir, filePath), "utf8");
6297
+ const source = await fs16.readFile(path6.join(rootDir, filePath), "utf8");
5705
6298
  const parsed = matter2(source);
5706
6299
  const markdown = parseMarkdownDocument(parsed.content);
5707
6300
  const title = markdown.headings.find((heading) => heading.depth === 1)?.text ?? null;
@@ -5724,7 +6317,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
5724
6317
 
5725
6318
  // src/repo-index/package-metadata.ts
5726
6319
  import fs17 from "fs/promises";
5727
- import path6 from "path";
6320
+ import path7 from "path";
5728
6321
  import fg7 from "fast-glob";
5729
6322
  var packageManagerFor = async (rootDir, packageDir) => {
5730
6323
  const lockfiles = [
@@ -5736,7 +6329,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
5736
6329
  ];
5737
6330
  for (const [lockfile, manager] of lockfiles) {
5738
6331
  try {
5739
- await fs17.access(path6.join(rootDir, packageDir, lockfile));
6332
+ await fs17.access(path7.join(rootDir, packageDir, lockfile));
5740
6333
  return manager;
5741
6334
  } catch {
5742
6335
  continue;
@@ -5753,8 +6346,8 @@ var discoverPackageMetadata = async (rootDir) => {
5753
6346
  });
5754
6347
  const packages = [];
5755
6348
  for (const packageFile of packageFiles.sort()) {
5756
- const packageDir = path6.posix.dirname(packageFile) === "." ? "" : path6.posix.dirname(packageFile);
5757
- const raw = JSON.parse(await fs17.readFile(path6.join(rootDir, packageFile), "utf8"));
6349
+ const packageDir = path7.posix.dirname(packageFile) === "." ? "" : path7.posix.dirname(packageFile);
6350
+ const raw = JSON.parse(await fs17.readFile(path7.join(rootDir, packageFile), "utf8"));
5758
6351
  const scripts = raw.scripts && typeof raw.scripts === "object" ? Object.keys(raw.scripts).sort() : [];
5759
6352
  packages.push({
5760
6353
  path: packageFile,
@@ -5813,16 +6406,16 @@ var declarationName = (node) => {
5813
6406
  }
5814
6407
  return node.name.text;
5815
6408
  };
5816
- var addExport = (exports, publicSymbols, path12, name, kind) => {
6409
+ var addExport = (exports, publicSymbols, path13, name, kind) => {
5817
6410
  if (!name) {
5818
6411
  return;
5819
6412
  }
5820
- const entry = { path: path12, name, kind };
6413
+ const entry = { path: path13, name, kind };
5821
6414
  exports.push(entry);
5822
6415
  publicSymbols.push(entry);
5823
6416
  };
5824
- var analyzeTypeScriptSource = (path12, source) => {
5825
- const sourceFile = ts.createSourceFile(path12, source, ts.ScriptTarget.Latest, true);
6417
+ var analyzeTypeScriptSource = (path13, source) => {
6418
+ const sourceFile = ts.createSourceFile(path13, source, ts.ScriptTarget.Latest, true);
5826
6419
  const imports = [];
5827
6420
  const exports = [];
5828
6421
  const publicSymbols = [];
@@ -5842,7 +6435,7 @@ var analyzeTypeScriptSource = (path12, source) => {
5842
6435
  }
5843
6436
  }
5844
6437
  imports.push({
5845
- from: path12,
6438
+ from: path13,
5846
6439
  specifier: statement.moduleSpecifier.text,
5847
6440
  imported: sortStrings(imported)
5848
6441
  });
@@ -5851,34 +6444,34 @@ var analyzeTypeScriptSource = (path12, source) => {
5851
6444
  if (ts.isExportDeclaration(statement)) {
5852
6445
  if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
5853
6446
  for (const element of statement.exportClause.elements) {
5854
- addExport(exports, publicSymbols, path12, element.name.text, "re-export");
6447
+ addExport(exports, publicSymbols, path13, element.name.text, "re-export");
5855
6448
  }
5856
6449
  }
5857
6450
  continue;
5858
6451
  }
5859
6452
  if (ts.isFunctionDeclaration(statement) && hasExportModifier(statement)) {
5860
- addExport(exports, publicSymbols, path12, declarationName(statement), "function");
6453
+ addExport(exports, publicSymbols, path13, declarationName(statement), "function");
5861
6454
  continue;
5862
6455
  }
5863
6456
  if (ts.isClassDeclaration(statement) && hasExportModifier(statement)) {
5864
- addExport(exports, publicSymbols, path12, declarationName(statement), "class");
6457
+ addExport(exports, publicSymbols, path13, declarationName(statement), "class");
5865
6458
  continue;
5866
6459
  }
5867
6460
  if (ts.isInterfaceDeclaration(statement) && hasExportModifier(statement)) {
5868
- addExport(exports, publicSymbols, path12, declarationName(statement), "interface");
6461
+ addExport(exports, publicSymbols, path13, declarationName(statement), "interface");
5869
6462
  continue;
5870
6463
  }
5871
6464
  if (ts.isTypeAliasDeclaration(statement) && hasExportModifier(statement)) {
5872
- addExport(exports, publicSymbols, path12, declarationName(statement), "type");
6465
+ addExport(exports, publicSymbols, path13, declarationName(statement), "type");
5873
6466
  continue;
5874
6467
  }
5875
6468
  if (ts.isEnumDeclaration(statement) && hasExportModifier(statement)) {
5876
- addExport(exports, publicSymbols, path12, declarationName(statement), "enum");
6469
+ addExport(exports, publicSymbols, path13, declarationName(statement), "enum");
5877
6470
  continue;
5878
6471
  }
5879
6472
  if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
5880
6473
  for (const declaration of statement.declarationList.declarations) {
5881
- addExport(exports, publicSymbols, path12, declarationName(declaration), "const");
6474
+ addExport(exports, publicSymbols, path13, declarationName(declaration), "const");
5882
6475
  }
5883
6476
  }
5884
6477
  }
@@ -5909,7 +6502,7 @@ var buildRepoIndex = async (cwd) => {
5909
6502
  if (!isJavaScriptLikePath(file.path)) {
5910
6503
  continue;
5911
6504
  }
5912
- const source = await fs18.readFile(path7.join(rootDir, file.path), "utf8");
6505
+ const source = await fs18.readFile(path8.join(rootDir, file.path), "utf8");
5913
6506
  const analysis = analyzeTypeScriptSource(file.path, source);
5914
6507
  imports.push(...analysis.imports);
5915
6508
  exports.push(...analysis.exports);
@@ -5941,7 +6534,7 @@ import { execa as execa4 } from "execa";
5941
6534
 
5942
6535
  // src/git/changes.ts
5943
6536
  import fs19 from "fs/promises";
5944
- import path8 from "path";
6537
+ import path9 from "path";
5945
6538
  import { execa as execa3 } from "execa";
5946
6539
  var normalizePath3 = (filePath) => {
5947
6540
  return filePath.replaceAll("\\", "/").replace(/^\.\//u, "");
@@ -5996,7 +6589,7 @@ var getUncommittedChanges = async (cwd) => {
5996
6589
  const deletedPathCandidates = /* @__PURE__ */ new Set([...stagedDeletedPaths, ...unstagedDeletedPaths]);
5997
6590
  for (const deletedPath of deletedPathCandidates) {
5998
6591
  const change = getOrCreateChange(changesByPath, deletedPath);
5999
- change.deleted = !await pathExists4(path8.join(rootDir, deletedPath));
6592
+ change.deleted = !await pathExists4(path9.join(rootDir, deletedPath));
6000
6593
  }
6001
6594
  return Array.from(changesByPath.values()).sort((left, right) => {
6002
6595
  return left.path.localeCompare(right.path);
@@ -6117,7 +6710,7 @@ var resolveImportPath = (importEdge) => {
6117
6710
  if (!importEdge.specifier.startsWith(".")) {
6118
6711
  return null;
6119
6712
  }
6120
- const basePath = path9.posix.normalize(path9.posix.join(path9.posix.dirname(importEdge.from), importEdge.specifier));
6713
+ const basePath = path10.posix.normalize(path10.posix.join(path10.posix.dirname(importEdge.from), importEdge.specifier));
6121
6714
  const withoutExtension = basePath.replace(/\.[cm]?[jt]sx?$/u, "");
6122
6715
  return withoutExtension;
6123
6716
  };
@@ -6130,7 +6723,7 @@ var importTargetsChangedFile = (importEdge, changedPath) => {
6130
6723
  };
6131
6724
  var pathSegments = (filePath) => filePath.split("/").filter(Boolean);
6132
6725
  var testHintMatchesChangedFile = (hints, changedPath) => {
6133
- const changedBaseName = path9.posix.basename(changedPath);
6726
+ const changedBaseName = path10.posix.basename(changedPath);
6134
6727
  const changedSegments = pathSegments(changedPath);
6135
6728
  return hints.some((hint) => changedBaseName.startsWith(hint) || changedSegments.includes(hint));
6136
6729
  };
@@ -6291,7 +6884,7 @@ import fg8 from "fast-glob";
6291
6884
 
6292
6885
  // src/evidence/parse.ts
6293
6886
  import fs20 from "fs/promises";
6294
- import path10 from "path";
6887
+ import path11 from "path";
6295
6888
  import matter3 from "gray-matter";
6296
6889
  import { parse as parse3 } from "yaml";
6297
6890
  var evidenceBlockPattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
@@ -6300,9 +6893,9 @@ var normalizeReferencePath = (truthDocPath, referencePath) => {
6300
6893
  const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
6301
6894
  const isRepoRelative = repoRootPrefixes.some((prefix) => strippedPath.startsWith(prefix));
6302
6895
  if (!isRepoRelative && (strippedPath.startsWith(".") || !strippedPath.includes("/"))) {
6303
- return path10.posix.normalize(path10.posix.join(path10.posix.dirname(truthDocPath), strippedPath));
6896
+ return path11.posix.normalize(path11.posix.join(path11.posix.dirname(truthDocPath), strippedPath));
6304
6897
  }
6305
- return path10.posix.normalize(strippedPath);
6898
+ return path11.posix.normalize(strippedPath);
6306
6899
  };
6307
6900
  var toEvidenceReference = (truthDocPath, raw) => {
6308
6901
  if (!raw || typeof raw !== "object" || !("path" in raw) || typeof raw.path !== "string") {
@@ -6319,7 +6912,7 @@ var toEvidenceReference = (truthDocPath, raw) => {
6319
6912
  };
6320
6913
  };
6321
6914
  var parseEvidenceReferences = async (rootDir, truthDocPath) => {
6322
- const source = await fs20.readFile(path10.join(rootDir, truthDocPath), "utf8");
6915
+ const source = await fs20.readFile(path11.join(rootDir, truthDocPath), "utf8");
6323
6916
  const parsed = matter3(source);
6324
6917
  const references = [];
6325
6918
  const sourceOfTruth = Array.isArray(parsed.data.source_of_truth) ? parsed.data.source_of_truth : [];
@@ -6557,7 +7150,7 @@ var runCheck = async (cwd, options = {}) => {
6557
7150
 
6558
7151
  // src/context-pack/build.ts
6559
7152
  import fs22 from "fs/promises";
6560
- import path11 from "path";
7153
+ import path12 from "path";
6561
7154
  import fg9 from "fast-glob";
6562
7155
  var uniqueSorted2 = (values) => [...new Set(values)].sort();
6563
7156
  var repoRootPrefixes2 = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
@@ -6568,12 +7161,12 @@ var normalizeDocReferencePath = (docPath, referencePath) => {
6568
7161
  return null;
6569
7162
  }
6570
7163
  const isRepoRelative = repoRootPrefixes2.some((prefix) => strippedPath.startsWith(prefix));
6571
- const normalized = isRepoRelative ? path11.posix.normalize(strippedPath) : path11.posix.normalize(path11.posix.join(path11.posix.dirname(docPath), strippedPath));
7164
+ const normalized = isRepoRelative ? path12.posix.normalize(strippedPath) : path12.posix.normalize(path12.posix.join(path12.posix.dirname(docPath), strippedPath));
6572
7165
  return normalized === ".." || normalized.startsWith("../") ? null : normalized;
6573
7166
  };
6574
7167
  var readIfExists = async (rootDir, filePath) => {
6575
7168
  try {
6576
- return await fs22.readFile(path11.join(rootDir, filePath), "utf8");
7169
+ return await fs22.readFile(path12.join(rootDir, filePath), "utf8");
6577
7170
  } catch {
6578
7171
  return null;
6579
7172
  }
@@ -7153,14 +7746,21 @@ var runContext = async (options) => {
7153
7746
  };
7154
7747
 
7155
7748
  // src/cli/program.ts
7749
+ var markFailedWhenErrorDiagnosticsExist = (result) => {
7750
+ if (result.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
7751
+ process.exitCode = 1;
7752
+ }
7753
+ };
7156
7754
  var writeResult = (result, options) => {
7157
7755
  const output = options.json ? renderJson(result) : renderHuman(result);
7158
7756
  process.stdout.write(`${output}
7159
7757
  `);
7758
+ markFailedWhenErrorDiagnosticsExist(result);
7160
7759
  };
7161
7760
  var writeContextResult = (result, options) => {
7162
7761
  if (!options.json && options.format === "markdown" && typeof result.data?.markdown === "string") {
7163
7762
  process.stdout.write(result.data.markdown);
7763
+ markFailedWhenErrorDiagnosticsExist(result);
7164
7764
  return;
7165
7765
  }
7166
7766
  writeResult(result, options);