truthmark 1.6.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -266,11 +266,11 @@ var DEFAULT_PLATFORMS = [
266
266
  var truthmarkConfigSchema = {
267
267
  type: "object",
268
268
  additionalProperties: false,
269
- required: ["version", "authority"],
269
+ required: ["version", "truthmark"],
270
270
  properties: {
271
271
  version: {
272
272
  type: "integer",
273
- const: 1
273
+ const: 2
274
274
  },
275
275
  platforms: {
276
276
  type: "array",
@@ -281,52 +281,58 @@ var truthmarkConfigSchema = {
281
281
  },
282
282
  minItems: 1
283
283
  },
284
- docs: {
284
+ truthmark: {
285
285
  type: "object",
286
- nullable: true,
287
286
  additionalProperties: false,
288
- required: ["layout", "roots", "routing"],
287
+ required: ["workspace", "routes", "truth", "templates", "generated"],
289
288
  properties: {
290
- layout: {
291
- type: "string",
292
- const: "hierarchical"
289
+ workspace: {
290
+ type: "string"
293
291
  },
294
- roots: {
292
+ routes: {
295
293
  type: "object",
296
- required: [],
297
- additionalProperties: {
298
- type: "string"
294
+ additionalProperties: false,
295
+ required: ["index", "areas", "default_area", "max_delegation_depth"],
296
+ properties: {
297
+ index: { type: "string" },
298
+ areas: { type: "string" },
299
+ default_area: { type: "string" },
300
+ max_delegation_depth: { type: "integer", const: 1 }
299
301
  }
300
302
  },
301
- routing: {
303
+ truth: {
302
304
  type: "object",
303
305
  additionalProperties: false,
304
- required: ["root_index", "area_files_root", "default_area", "max_delegation_depth"],
306
+ required: ["root"],
305
307
  properties: {
306
- root_index: {
307
- type: "string"
308
- },
309
- area_files_root: {
310
- type: "string"
311
- },
312
- default_area: {
313
- type: "string"
314
- },
315
- max_delegation_depth: {
316
- type: "integer",
317
- const: 1
308
+ root: { type: "string" }
309
+ }
310
+ },
311
+ templates: {
312
+ type: "object",
313
+ additionalProperties: false,
314
+ required: ["root"],
315
+ properties: {
316
+ root: { type: "string" }
317
+ }
318
+ },
319
+ generated: {
320
+ type: "object",
321
+ additionalProperties: false,
322
+ required: ["portal"],
323
+ properties: {
324
+ portal: {
325
+ type: "object",
326
+ additionalProperties: false,
327
+ required: ["enabled"],
328
+ properties: {
329
+ enabled: { type: "boolean" }
330
+ }
318
331
  }
319
332
  }
320
333
  }
321
334
  }
322
335
  },
323
- authority: {
324
- type: "array",
325
- items: {
326
- type: "string"
327
- },
328
- minItems: 1
329
- },
330
336
  instruction_targets: {
331
337
  type: "array",
332
338
  nullable: true,
@@ -334,22 +340,6 @@ var truthmarkConfigSchema = {
334
340
  type: "string"
335
341
  }
336
342
  },
337
- "truthmark-portal": {
338
- type: "object",
339
- additionalProperties: false,
340
- required: [],
341
- properties: {
342
- enabled: {
343
- type: "boolean"
344
- },
345
- output: {
346
- type: "string"
347
- },
348
- template: {
349
- type: "string"
350
- }
351
- }
352
- },
353
343
  frontmatter: {
354
344
  type: "object",
355
345
  nullable: true,
@@ -383,44 +373,39 @@ var truthmarkConfigSchema = {
383
373
  };
384
374
 
385
375
  // src/config/defaults.ts
386
- var DEFAULT_DOCS_HIERARCHY = {
387
- layout: "hierarchical",
388
- roots: {
389
- ai: "docs/ai",
390
- standards: "docs/standards",
391
- architecture: "docs/architecture",
392
- truth: "docs/truth"
393
- },
394
- routing: {
395
- root_index: "docs/truthmark/areas.md",
396
- area_files_root: "docs/truthmark/areas",
376
+ var DEFAULT_TRUTHMARK_WORKSPACE = {
377
+ workspace: "docs/truthmark",
378
+ routes: {
379
+ index: "routes/areas.md",
380
+ areas: "routes/areas",
397
381
  default_area: "repository",
398
382
  max_delegation_depth: 1
383
+ },
384
+ truth: {
385
+ root: "truth"
386
+ },
387
+ templates: {
388
+ root: "templates"
389
+ },
390
+ generated: {
391
+ portal: {
392
+ enabled: false
393
+ }
399
394
  }
400
395
  };
401
- var DEFAULT_AUTHORITY = [
402
- DEFAULT_DOCS_HIERARCHY.routing.root_index,
403
- `${DEFAULT_DOCS_HIERARCHY.routing.area_files_root}/**/*.md`,
404
- `${DEFAULT_DOCS_HIERARCHY.roots.ai}/**/*.md`,
405
- `${DEFAULT_DOCS_HIERARCHY.roots.standards}/**/*.md`,
406
- `${DEFAULT_DOCS_HIERARCHY.roots.architecture}/**/*.md`,
407
- `${DEFAULT_DOCS_HIERARCHY.roots.truth}/**/*.md`
408
- ];
409
396
  var DEFAULT_INSTRUCTION_TARGETS = ["AGENTS.md"];
410
- var DEFAULT_TRUTHMARK_PORTAL = {
411
- enabled: false,
412
- output: "docs/truthmark-portal",
413
- template: "default"
414
- };
415
397
  var createDefaultRawConfig = () => ({
416
- version: 1,
398
+ version: 2,
417
399
  platforms: [...DEFAULT_PLATFORMS],
418
- docs: {
419
- layout: DEFAULT_DOCS_HIERARCHY.layout,
420
- roots: { ...DEFAULT_DOCS_HIERARCHY.roots },
421
- routing: { ...DEFAULT_DOCS_HIERARCHY.routing }
400
+ truthmark: {
401
+ workspace: DEFAULT_TRUTHMARK_WORKSPACE.workspace,
402
+ routes: { ...DEFAULT_TRUTHMARK_WORKSPACE.routes },
403
+ truth: { ...DEFAULT_TRUTHMARK_WORKSPACE.truth },
404
+ templates: { ...DEFAULT_TRUTHMARK_WORKSPACE.templates },
405
+ generated: {
406
+ portal: { ...DEFAULT_TRUTHMARK_WORKSPACE.generated.portal }
407
+ }
422
408
  },
423
- authority: [...DEFAULT_AUTHORITY],
424
409
  instruction_targets: [...DEFAULT_INSTRUCTION_TARGETS],
425
410
  frontmatter: {
426
411
  required: [],
@@ -429,21 +414,37 @@ var createDefaultRawConfig = () => ({
429
414
  ignore: ["node_modules/**", "vendor/**", "dist/**", "build/**"]
430
415
  });
431
416
  var createDefaultConfig = () => ({
432
- version: 1,
417
+ version: 2,
433
418
  platforms: [...DEFAULT_PLATFORMS],
434
- docs: {
435
- layout: DEFAULT_DOCS_HIERARCHY.layout,
436
- roots: { ...DEFAULT_DOCS_HIERARCHY.roots },
437
- routing: {
438
- rootIndex: DEFAULT_DOCS_HIERARCHY.routing.root_index,
439
- areaFilesRoot: DEFAULT_DOCS_HIERARCHY.routing.area_files_root,
440
- defaultArea: DEFAULT_DOCS_HIERARCHY.routing.default_area,
441
- maxDelegationDepth: DEFAULT_DOCS_HIERARCHY.routing.max_delegation_depth
442
- }
419
+ truthmark: {
420
+ workspace: DEFAULT_TRUTHMARK_WORKSPACE.workspace,
421
+ routes: {
422
+ index: DEFAULT_TRUTHMARK_WORKSPACE.routes.index,
423
+ areas: DEFAULT_TRUTHMARK_WORKSPACE.routes.areas,
424
+ defaultArea: DEFAULT_TRUTHMARK_WORKSPACE.routes.default_area,
425
+ maxDelegationDepth: DEFAULT_TRUTHMARK_WORKSPACE.routes.max_delegation_depth
426
+ },
427
+ truth: { root: DEFAULT_TRUTHMARK_WORKSPACE.truth.root },
428
+ templates: { root: DEFAULT_TRUTHMARK_WORKSPACE.templates.root },
429
+ generated: {
430
+ portal: { ...DEFAULT_TRUTHMARK_WORKSPACE.generated.portal }
431
+ },
432
+ paths: {
433
+ routesIndex: "docs/truthmark/routes/areas.md",
434
+ routeAreasRoot: "docs/truthmark/routes/areas",
435
+ truthRoot: "docs/truthmark/truth",
436
+ templatesRoot: "docs/truthmark/templates",
437
+ portalOutput: "docs/truthmark/generated/portal",
438
+ portalTemplate: "docs/truthmark/templates/portal.html"
439
+ },
440
+ controlledPaths: [
441
+ "docs/truthmark/routes/areas.md",
442
+ "docs/truthmark/routes/areas/**/*.md",
443
+ "docs/truthmark/truth/**/*.md",
444
+ "docs/truthmark/templates/*.md"
445
+ ]
443
446
  },
444
- authority: [...DEFAULT_AUTHORITY],
445
447
  instructionTargets: [...DEFAULT_INSTRUCTION_TARGETS],
446
- truthmarkPortal: { ...DEFAULT_TRUTHMARK_PORTAL },
447
448
  frontmatter: {
448
449
  required: [],
449
450
  recommended: ["status", "doc_type", "last_reviewed", "source_of_truth"]
@@ -461,6 +462,7 @@ var TRUTH_DOCUMENT_KINDS = [
461
462
  "operations",
462
463
  "test-behavior"
463
464
  ];
465
+ var DEFAULT_WORKSPACE_TRUTH_DOCS_ROOT = "docs/truthmark/truth";
464
466
  var slugify = (value) => {
465
467
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
466
468
  };
@@ -480,25 +482,10 @@ var isTruthDocumentKind = (value) => {
480
482
  };
481
483
  var inferTruthDocumentKindFromPath = (documentPath, options = {}) => {
482
484
  const normalizedPath = documentPath.replaceAll("\\", "/");
483
- const truthDocsRoot = options.truthDocsRoot?.replaceAll("\\", "/").replace(/\/+$/u, "");
484
- if (truthDocsRoot && normalizedPath.startsWith(`${truthDocsRoot}/`) || normalizedPath.startsWith("docs/truth/")) {
485
+ const truthDocsRoot = (options.truthDocsRoot ?? DEFAULT_WORKSPACE_TRUTH_DOCS_ROOT)?.replaceAll("\\", "/").replace(/\/+$/u, "");
486
+ if (truthDocsRoot && normalizedPath.startsWith(`${truthDocsRoot}/`)) {
485
487
  return "behavior";
486
488
  }
487
- if (normalizedPath.startsWith("docs/contracts/") || normalizedPath.startsWith("docs/contract/") || normalizedPath.startsWith("docs/api/")) {
488
- return "contract";
489
- }
490
- if (normalizedPath.startsWith("docs/architecture/")) {
491
- return "architecture";
492
- }
493
- if (normalizedPath.startsWith("docs/workflows/") || normalizedPath.startsWith("docs/workflow/")) {
494
- return "workflow";
495
- }
496
- if (normalizedPath.startsWith("docs/operations/") || normalizedPath.startsWith("docs/platform/")) {
497
- return "operations";
498
- }
499
- if (normalizedPath.startsWith("docs/testing/") || normalizedPath.startsWith("docs/tests/")) {
500
- return "test-behavior";
501
- }
502
489
  return null;
503
490
  };
504
491
  var findTruthDocumentsYamlFenceRange = (sectionLines) => {
@@ -738,9 +725,8 @@ var parseAreasMarkdown = (source, options = {}) => {
738
725
  };
739
726
 
740
727
  // src/truth/docs.ts
741
- var DEFAULT_TRUTH_DOCS_ROOT = DEFAULT_DOCS_HIERARCHY.roots.truth;
742
728
  var resolveTruthDocsRoot = (config) => {
743
- return config.docs.roots.truth ?? DEFAULT_TRUTH_DOCS_ROOT;
729
+ return config.truthmark.paths.truthRoot;
744
730
  };
745
731
 
746
732
  // src/templates/init-files.ts
@@ -759,11 +745,11 @@ var titleCase = (value) => {
759
745
  return value.split(/[-_\s]+/u).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
760
746
  };
761
747
  var renderHierarchicalAreasIndexTemplate = (config) => {
762
- const defaultArea = config.docs.routing.defaultArea;
763
- const childPath = `${config.docs.routing.areaFilesRoot}/${defaultArea}.md`;
748
+ const defaultArea = config.truthmark.routes.defaultArea;
749
+ const childPath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
764
750
  const title = titleCase(defaultArea);
765
751
  const sourceOfTruth = resolveRelativePath(
766
- config.docs.routing.rootIndex,
752
+ config.truthmark.paths.routesIndex,
767
753
  ".truthmark/config.yml"
768
754
  );
769
755
  return [
@@ -792,11 +778,11 @@ var renderHierarchicalAreasIndexTemplate = (config) => {
792
778
  ].join("\n");
793
779
  };
794
780
  var renderChildAreaTemplate = (config) => {
795
- const defaultArea = config.docs.routing.defaultArea;
781
+ const defaultArea = config.truthmark.routes.defaultArea;
796
782
  const title = titleCase(defaultArea);
797
783
  const truthDocsRoot = truthRoot(config);
798
784
  const leafTruthDoc = `${truthDocsRoot}/${defaultArea}/overview.md`;
799
- const templatePath = `${config.docs.routing.areaFilesRoot}/${defaultArea}.md`;
785
+ const templatePath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
800
786
  const sourceOfTruth = resolveRelativePath(templatePath, ".truthmark/config.yml");
801
787
  return [
802
788
  "---",
@@ -830,7 +816,7 @@ var renderTruthRootReadmeTemplate = (config = createDefaultConfig()) => {
830
816
  const templatePath = `${truthRoot(config)}/README.md`;
831
817
  const sourceOfTruth = resolveRelativePath(
832
818
  templatePath,
833
- config.docs.routing.rootIndex
819
+ config.truthmark.paths.routesIndex
834
820
  );
835
821
  return [
836
822
  "---",
@@ -850,12 +836,12 @@ var renderTruthRootReadmeTemplate = (config = createDefaultConfig()) => {
850
836
  ].join("\n");
851
837
  };
852
838
  var renderTruthDomainReadmeTemplate = (config) => {
853
- const defaultArea = config.docs.routing.defaultArea;
839
+ const defaultArea = config.truthmark.routes.defaultArea;
854
840
  const title = titleCase(defaultArea);
855
841
  const templatePath = `${truthRoot(config)}/${defaultArea}/README.md`;
856
842
  const sourceOfTruth = resolveRelativePath(
857
843
  templatePath,
858
- `${config.docs.routing.areaFilesRoot}/${defaultArea}.md`
844
+ `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`
859
845
  );
860
846
  return [
861
847
  "---",
@@ -878,12 +864,7 @@ var renderTruthDomainReadmeTemplate = (config) => {
878
864
  ""
879
865
  ].join("\n");
880
866
  };
881
- var BEHAVIOR_DOC_TEMPLATE_PATH = "docs/templates/behavior-doc.md";
882
- var CONTRACT_DOC_TEMPLATE_PATH = "docs/templates/contract-doc.md";
883
- var ARCHITECTURE_DOC_TEMPLATE_PATH = "docs/templates/architecture-doc.md";
884
- var WORKFLOW_DOC_TEMPLATE_PATH = "docs/templates/workflow-doc.md";
885
- var OPERATIONS_DOC_TEMPLATE_PATH = "docs/templates/operations-doc.md";
886
- var TEST_BEHAVIOR_DOC_TEMPLATE_PATH = "docs/templates/test-behavior-doc.md";
867
+ var BEHAVIOR_DOC_TEMPLATE_PATH = "docs/truthmark/templates/behavior-doc.md";
887
868
  var renderTemplateSection = (section) => {
888
869
  return [
889
870
  section.heading,
@@ -1286,12 +1267,12 @@ var renderTemplate = (template, values) => {
1286
1267
  }, template);
1287
1268
  };
1288
1269
  var renderBehaviorLeafDocTemplate = (config, template = renderBehaviorDocTemplateFile()) => {
1289
- const defaultArea = config.docs.routing.defaultArea;
1270
+ const defaultArea = config.truthmark.routes.defaultArea;
1290
1271
  const title = titleCase(defaultArea);
1291
1272
  const templatePath = `${truthRoot(config)}/${defaultArea}/overview.md`;
1292
1273
  const sourceOfTruth = resolveRelativePath(
1293
1274
  templatePath,
1294
- `${config.docs.routing.areaFilesRoot}/${defaultArea}.md`
1275
+ `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`
1295
1276
  );
1296
1277
  const today = currentDate();
1297
1278
  return renderTemplate(template, {
@@ -1399,18 +1380,15 @@ import { Ajv } from "ajv";
1399
1380
  import { parse as parse2 } from "yaml";
1400
1381
  var ajv = new Ajv({ allErrors: true });
1401
1382
  var validateTruthmarkConfig = ajv.compile(truthmarkConfigSchema);
1402
- var toConfigDiagnostic = (message, file) => {
1403
- return {
1404
- category: "config",
1405
- severity: "error",
1406
- message,
1407
- file
1408
- };
1409
- };
1383
+ var toConfigDiagnostic = (message, file) => ({
1384
+ category: "config",
1385
+ severity: "error",
1386
+ message,
1387
+ file
1388
+ });
1410
1389
  var normalizeRepoRelativePath = (value) => {
1411
1390
  const slashNormalized = value.replace(/\\/gu, "/");
1412
- const pathNormalized = path4.posix.normalize(slashNormalized).replace(/\/+$/u, "");
1413
- return pathNormalized;
1391
+ return path4.posix.normalize(slashNormalized).replace(/\/+$/u, "");
1414
1392
  };
1415
1393
  var isUnsafeRepoRelativePath = (value) => {
1416
1394
  const slashNormalized = value.replace(/\\/gu, "/");
@@ -1418,85 +1396,153 @@ var isUnsafeRepoRelativePath = (value) => {
1418
1396
  const parts = slashNormalized.split("/");
1419
1397
  return normalized.length === 0 || normalized === "." || normalized === ".." || path4.isAbsolute(value) || path4.posix.isAbsolute(slashNormalized) || path4.win32.isAbsolute(value) || /^[A-Za-z]:/u.test(value) || normalized.startsWith("../") || parts.includes("..");
1420
1398
  };
1399
+ var joinWorkspacePath = (workspace, childPath) => {
1400
+ return normalizeRepoRelativePath(`${workspace}/${childPath}`);
1401
+ };
1402
+ var portalOutputFor = (workspace) => joinWorkspacePath(workspace, "generated/portal");
1403
+ var portalTemplateFor = (templatesRoot) => joinWorkspacePath(templatesRoot, "portal.html");
1421
1404
  var pathsOverlap = (left, right) => {
1422
1405
  const normalizedLeft = normalizeRepoRelativePath(left);
1423
1406
  const normalizedRight = normalizeRepoRelativePath(right);
1424
1407
  return normalizedLeft === normalizedRight || normalizedLeft.startsWith(`${normalizedRight}/`) || normalizedRight.startsWith(`${normalizedLeft}/`);
1425
1408
  };
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) {
1409
+ var CONFIG_PATH2 = ".truthmark/config.yml";
1410
+ var FORBIDDEN_WORKSPACE_OVERLAPS = [
1411
+ ".git",
1412
+ ".truthmark",
1413
+ "package.json",
1414
+ "package-lock.json",
1415
+ "pnpm-lock.yaml",
1416
+ "yarn.lock",
1417
+ "src",
1418
+ "tests"
1419
+ ];
1420
+ var unsupportedShapeDiagnostics = (parsedConfig, configPath) => {
1421
+ if (!parsedConfig || typeof parsedConfig !== "object" || Array.isArray(parsedConfig)) {
1450
1422
  return [];
1451
1423
  }
1424
+ const record = parsedConfig;
1452
1425
  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))) {
1426
+ if (record.version !== 2) {
1456
1427
  diagnostics.push(
1457
1428
  toConfigDiagnostic(
1458
- "truthmark-portal.output must be a non-empty repo-relative directory that does not overlap source, instruction, routing, or canonical docs roots.",
1429
+ "Unsupported Truthmark config shape. This release requires version: 2 with a truthmark workspace block.",
1459
1430
  configPath
1460
1431
  )
1461
1432
  );
1462
1433
  }
1463
- if (template !== "default" && isUnsafeRepoRelativePath(template)) {
1434
+ if ("docs" in record || "authority" in record) {
1464
1435
  diagnostics.push(
1465
1436
  toConfigDiagnostic(
1466
- "truthmark-portal.template must be 'default' or a non-empty repo-relative template path without absolute or parent traversal segments.",
1437
+ "Unsupported Truthmark config shape. Remove old docs.roots and legacy authority settings; use version: 2 truthmark.workspace paths.",
1467
1438
  configPath
1468
1439
  )
1469
1440
  );
1470
1441
  }
1471
1442
  return diagnostics;
1472
1443
  };
1444
+ var validateWorkspacePaths = (rawConfig, configPath) => {
1445
+ const diagnostics = [];
1446
+ const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
1447
+ const childPaths = [
1448
+ ["truthmark.routes.index", rawConfig.truthmark.routes.index],
1449
+ ["truthmark.routes.areas", rawConfig.truthmark.routes.areas],
1450
+ ["truthmark.truth.root", rawConfig.truthmark.truth.root],
1451
+ ["truthmark.templates.root", rawConfig.truthmark.templates.root]
1452
+ ];
1453
+ if (isUnsafeRepoRelativePath(rawConfig.truthmark.workspace) || FORBIDDEN_WORKSPACE_OVERLAPS.some((forbidden) => pathsOverlap(workspace, forbidden))) {
1454
+ diagnostics.push(
1455
+ toConfigDiagnostic(
1456
+ "truthmark.workspace must be a non-empty repo-relative directory that does not overlap repository control, package, source, test, or instruction paths.",
1457
+ configPath
1458
+ )
1459
+ );
1460
+ }
1461
+ for (const [name, value] of childPaths) {
1462
+ if (isUnsafeRepoRelativePath(value)) {
1463
+ diagnostics.push(
1464
+ toConfigDiagnostic(
1465
+ `${name} must be a non-empty path relative to truthmark.workspace without absolute or parent traversal segments.`,
1466
+ configPath
1467
+ )
1468
+ );
1469
+ }
1470
+ }
1471
+ const portalOutput = normalizeRepoRelativePath("generated/portal");
1472
+ const controlledWorkspaceChildren = [
1473
+ ["truthmark.routes.index", rawConfig.truthmark.routes.index],
1474
+ ["truthmark.routes.areas", rawConfig.truthmark.routes.areas],
1475
+ ["truthmark.truth.root", rawConfig.truthmark.truth.root],
1476
+ ["truthmark.templates.root", rawConfig.truthmark.templates.root]
1477
+ ];
1478
+ for (const [name, value] of controlledWorkspaceChildren) {
1479
+ if (pathsOverlap(portalOutput, value)) {
1480
+ diagnostics.push(
1481
+ toConfigDiagnostic(
1482
+ `Truthmark Portal output ${portalOutputFor(workspace)} must not overlap ${name}; Portal output is generated and must stay outside controlled truth, routing, and template paths.`,
1483
+ configPath
1484
+ )
1485
+ );
1486
+ }
1487
+ }
1488
+ for (const target of rawConfig.instruction_targets ?? DEFAULT_INSTRUCTION_TARGETS) {
1489
+ if (isUnsafeRepoRelativePath(target) || pathsOverlap(workspace, target)) {
1490
+ diagnostics.push(
1491
+ toConfigDiagnostic(
1492
+ "instruction_targets must be repo-relative files outside truthmark.workspace.",
1493
+ configPath
1494
+ )
1495
+ );
1496
+ }
1497
+ }
1498
+ return diagnostics;
1499
+ };
1473
1500
  var normalizeConfig = (rawConfig) => {
1474
- const rawDocs = rawConfig.docs ?? {
1475
- layout: DEFAULT_DOCS_HIERARCHY.layout,
1476
- roots: { ...DEFAULT_DOCS_HIERARCHY.roots },
1477
- routing: { ...DEFAULT_DOCS_HIERARCHY.routing }
1478
- };
1479
- const roots = { ...DEFAULT_DOCS_HIERARCHY.roots, ...rawDocs.roots };
1501
+ const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
1502
+ const routesIndex = joinWorkspacePath(workspace, rawConfig.truthmark.routes.index);
1503
+ const routeAreasRoot = joinWorkspacePath(workspace, rawConfig.truthmark.routes.areas);
1504
+ const truthRoot3 = joinWorkspacePath(workspace, rawConfig.truthmark.truth.root);
1505
+ const templatesRoot = joinWorkspacePath(workspace, rawConfig.truthmark.templates.root);
1506
+ const portalOutput = portalOutputFor(workspace);
1507
+ const portalTemplate = portalTemplateFor(templatesRoot);
1480
1508
  return {
1481
1509
  version: rawConfig.version,
1482
1510
  platforms: rawConfig.platforms ?? [...DEFAULT_PLATFORMS],
1483
- docs: {
1484
- layout: rawDocs.layout,
1485
- roots,
1486
- routing: {
1487
- rootIndex: rawDocs.routing.root_index,
1488
- areaFilesRoot: rawDocs.routing.area_files_root,
1489
- defaultArea: rawDocs.routing.default_area,
1490
- maxDelegationDepth: rawDocs.routing.max_delegation_depth
1491
- }
1511
+ truthmark: {
1512
+ workspace,
1513
+ routes: {
1514
+ index: normalizeRepoRelativePath(rawConfig.truthmark.routes.index),
1515
+ areas: normalizeRepoRelativePath(rawConfig.truthmark.routes.areas),
1516
+ defaultArea: rawConfig.truthmark.routes.default_area,
1517
+ maxDelegationDepth: rawConfig.truthmark.routes.max_delegation_depth
1518
+ },
1519
+ truth: {
1520
+ root: normalizeRepoRelativePath(rawConfig.truthmark.truth.root)
1521
+ },
1522
+ templates: {
1523
+ root: normalizeRepoRelativePath(rawConfig.truthmark.templates.root)
1524
+ },
1525
+ generated: {
1526
+ portal: {
1527
+ enabled: rawConfig.truthmark.generated.portal.enabled
1528
+ }
1529
+ },
1530
+ paths: {
1531
+ routesIndex,
1532
+ routeAreasRoot,
1533
+ truthRoot: truthRoot3,
1534
+ templatesRoot,
1535
+ portalOutput,
1536
+ portalTemplate
1537
+ },
1538
+ controlledPaths: [
1539
+ routesIndex,
1540
+ `${routeAreasRoot}/**/*.md`,
1541
+ `${truthRoot3}/**/*.md`,
1542
+ `${templatesRoot}/*.md`
1543
+ ]
1492
1544
  },
1493
- authority: rawConfig.authority,
1494
1545
  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
- },
1500
1546
  frontmatter: {
1501
1547
  required: rawConfig.frontmatter?.required ?? [],
1502
1548
  recommended: rawConfig.frontmatter?.recommended ?? []
@@ -1505,8 +1551,7 @@ var normalizeConfig = (rawConfig) => {
1505
1551
  };
1506
1552
  };
1507
1553
  var loadConfig = async (rootDir) => {
1508
- const configPath = ".truthmark/config.yml";
1509
- const absolutePath = resolveRepoPath(rootDir, configPath);
1554
+ const absolutePath = resolveRepoPath(rootDir, CONFIG_PATH2);
1510
1555
  let source;
1511
1556
  try {
1512
1557
  source = await fs4.readFile(absolutePath, "utf8");
@@ -1515,8 +1560,8 @@ var loadConfig = async (rootDir) => {
1515
1560
  return {
1516
1561
  status: "missing",
1517
1562
  config: null,
1518
- diagnostics: [toConfigDiagnostic("Missing .truthmark/config.yml.", configPath)],
1519
- configPath
1563
+ diagnostics: [toConfigDiagnostic("Missing .truthmark/config.yml.", CONFIG_PATH2)],
1564
+ configPath: CONFIG_PATH2
1520
1565
  };
1521
1566
  }
1522
1567
  throw error;
@@ -1531,10 +1576,19 @@ var loadConfig = async (rootDir) => {
1531
1576
  diagnostics: [
1532
1577
  toConfigDiagnostic(
1533
1578
  `Invalid YAML: ${error instanceof Error ? error.message : String(error)}`,
1534
- configPath
1579
+ CONFIG_PATH2
1535
1580
  )
1536
1581
  ],
1537
- configPath
1582
+ configPath: CONFIG_PATH2
1583
+ };
1584
+ }
1585
+ const unsupportedDiagnostics = unsupportedShapeDiagnostics(parsedConfig, CONFIG_PATH2);
1586
+ if (unsupportedDiagnostics.length > 0) {
1587
+ return {
1588
+ status: "invalid",
1589
+ config: null,
1590
+ diagnostics: unsupportedDiagnostics,
1591
+ configPath: CONFIG_PATH2
1538
1592
  };
1539
1593
  }
1540
1594
  if (!validateTruthmarkConfig(parsedConfig)) {
@@ -1545,49 +1599,30 @@ var loadConfig = async (rootDir) => {
1545
1599
  const propertyPath = error.instancePath || "/";
1546
1600
  const additionalProperty = error.keyword === "additionalProperties" && error.params && "additionalProperty" in error.params ? String(error.params.additionalProperty) : null;
1547
1601
  const message = additionalProperty ? `${propertyPath} additional property ${additionalProperty} is not allowed` : `${propertyPath} ${error.message ?? "is invalid"}`.trim();
1548
- return toConfigDiagnostic(message, configPath);
1602
+ return toConfigDiagnostic(message, CONFIG_PATH2);
1549
1603
  }),
1550
- configPath
1604
+ configPath: CONFIG_PATH2
1551
1605
  };
1552
1606
  }
1553
- const portalDiagnostics = validatePortalConfig(
1554
- parsedConfig,
1555
- configPath
1556
- );
1557
- if (portalDiagnostics.length > 0) {
1607
+ const pathDiagnostics = validateWorkspacePaths(parsedConfig, CONFIG_PATH2);
1608
+ if (pathDiagnostics.length > 0) {
1558
1609
  return {
1559
1610
  status: "invalid",
1560
1611
  config: null,
1561
- diagnostics: portalDiagnostics,
1562
- configPath
1612
+ diagnostics: pathDiagnostics,
1613
+ configPath: CONFIG_PATH2
1563
1614
  };
1564
1615
  }
1565
1616
  return {
1566
1617
  status: "loaded",
1567
1618
  config: normalizeConfig(parsedConfig),
1568
1619
  diagnostics: [],
1569
- configPath
1620
+ configPath: CONFIG_PATH2
1570
1621
  };
1571
1622
  };
1572
1623
 
1573
1624
  // src/init/hierarchy.ts
1574
1625
  import fs5 from "fs/promises";
1575
- import fg from "fast-glob";
1576
- var KNOWN_DEFAULT_ROOTS = [
1577
- DEFAULT_DOCS_HIERARCHY.roots.truth,
1578
- "docs/api",
1579
- DEFAULT_DOCS_HIERARCHY.roots.architecture,
1580
- DEFAULT_DOCS_HIERARCHY.roots.standards,
1581
- "docs/guides"
1582
- ];
1583
- var hasMarkdownFiles = async (rootDir, root) => {
1584
- const matches = await fg([`${root}/**/*.md`], {
1585
- cwd: rootDir,
1586
- onlyFiles: true,
1587
- followSymbolicLinks: false
1588
- });
1589
- return matches.length > 0;
1590
- };
1591
1626
  var truthRoot2 = resolveTruthDocsRoot;
1592
1627
  var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePath) => {
1593
1628
  const rootIndexSource = await fs5.readFile(resolveRepoPath(rootDir, rootIndexPath), "utf8");
@@ -1596,9 +1631,15 @@ var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePat
1596
1631
  (areaReference) => areaReference.areaFiles.includes(childRoutePath)
1597
1632
  );
1598
1633
  };
1599
- var readBehaviorDocTemplate = async (rootDir) => {
1634
+ var truthTemplatePath = (config, fileName) => {
1635
+ return `${config.truthmark.paths.templatesRoot}/${fileName}`;
1636
+ };
1637
+ var readBehaviorDocTemplate = async (rootDir, config) => {
1600
1638
  try {
1601
- return await fs5.readFile(resolveRepoPath(rootDir, BEHAVIOR_DOC_TEMPLATE_PATH), "utf8");
1639
+ return await fs5.readFile(
1640
+ resolveRepoPath(rootDir, truthTemplatePath(config, "behavior-doc.md")),
1641
+ "utf8"
1642
+ );
1602
1643
  } catch (error) {
1603
1644
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1604
1645
  return renderBehaviorDocTemplateFile();
@@ -1618,16 +1659,16 @@ var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTempla
1618
1659
  var scaffoldHierarchy = async (rootDir, config) => {
1619
1660
  const results = [];
1620
1661
  const truthDocsRoot = truthRoot2(config);
1621
- const truthDomainRoot = `${truthDocsRoot}/${config.docs.routing.defaultArea}`;
1622
- const childRoutePath = `${config.docs.routing.areaFilesRoot}/${config.docs.routing.defaultArea}.md`;
1662
+ const truthDomainRoot = `${truthDocsRoot}/${config.truthmark.routes.defaultArea}`;
1663
+ const childRoutePath = `${config.truthmark.paths.routeAreasRoot}/${config.truthmark.routes.defaultArea}.md`;
1623
1664
  results.push(
1624
1665
  await ensureRepoFile(
1625
1666
  rootDir,
1626
- config.docs.routing.rootIndex,
1667
+ config.truthmark.paths.routesIndex,
1627
1668
  renderHierarchicalAreasIndexTemplate(config)
1628
1669
  )
1629
1670
  );
1630
- if (await rootIndexReferencesChildRoute(rootDir, config.docs.routing.rootIndex, childRoutePath)) {
1671
+ if (await rootIndexReferencesChildRoute(rootDir, config.truthmark.paths.routesIndex, childRoutePath)) {
1631
1672
  results.push(await ensureRepoFile(rootDir, childRoutePath, renderChildAreaTemplate(config)));
1632
1673
  }
1633
1674
  results.push(
@@ -1647,46 +1688,46 @@ var scaffoldHierarchy = async (rootDir, config) => {
1647
1688
  results.push(
1648
1689
  await ensureOrUpdateTruthDocTemplate(
1649
1690
  rootDir,
1650
- BEHAVIOR_DOC_TEMPLATE_PATH,
1691
+ truthTemplatePath(config, "behavior-doc.md"),
1651
1692
  renderBehaviorDocTemplateFile()
1652
1693
  )
1653
1694
  );
1654
1695
  results.push(
1655
1696
  await ensureOrUpdateTruthDocTemplate(
1656
1697
  rootDir,
1657
- CONTRACT_DOC_TEMPLATE_PATH,
1698
+ truthTemplatePath(config, "contract-doc.md"),
1658
1699
  renderContractDocTemplateFile()
1659
1700
  )
1660
1701
  );
1661
1702
  results.push(
1662
1703
  await ensureOrUpdateTruthDocTemplate(
1663
1704
  rootDir,
1664
- ARCHITECTURE_DOC_TEMPLATE_PATH,
1705
+ truthTemplatePath(config, "architecture-doc.md"),
1665
1706
  renderArchitectureDocTemplateFile()
1666
1707
  )
1667
1708
  );
1668
1709
  results.push(
1669
1710
  await ensureOrUpdateTruthDocTemplate(
1670
1711
  rootDir,
1671
- WORKFLOW_DOC_TEMPLATE_PATH,
1712
+ truthTemplatePath(config, "workflow-doc.md"),
1672
1713
  renderWorkflowDocTemplateFile()
1673
1714
  )
1674
1715
  );
1675
1716
  results.push(
1676
1717
  await ensureOrUpdateTruthDocTemplate(
1677
1718
  rootDir,
1678
- OPERATIONS_DOC_TEMPLATE_PATH,
1719
+ truthTemplatePath(config, "operations-doc.md"),
1679
1720
  renderOperationsDocTemplateFile()
1680
1721
  )
1681
1722
  );
1682
1723
  results.push(
1683
1724
  await ensureOrUpdateTruthDocTemplate(
1684
1725
  rootDir,
1685
- TEST_BEHAVIOR_DOC_TEMPLATE_PATH,
1726
+ truthTemplatePath(config, "test-behavior-doc.md"),
1686
1727
  renderTestBehaviorDocTemplateFile()
1687
1728
  )
1688
1729
  );
1689
- const behaviorDocTemplate = await readBehaviorDocTemplate(rootDir);
1730
+ const behaviorDocTemplate = await readBehaviorDocTemplate(rootDir, config);
1690
1731
  results.push(
1691
1732
  await ensureRepoFile(
1692
1733
  rootDir,
@@ -1696,24 +1737,6 @@ var scaffoldHierarchy = async (rootDir, config) => {
1696
1737
  );
1697
1738
  return results;
1698
1739
  };
1699
- var detectHierarchyMigrationDiagnostics = async (rootDir, config) => {
1700
- const configuredRoots = new Set(Object.values(config.docs.roots));
1701
- const diagnostics = [];
1702
- for (const defaultRoot of KNOWN_DEFAULT_ROOTS) {
1703
- if (configuredRoots.has(defaultRoot)) {
1704
- continue;
1705
- }
1706
- if (await hasMarkdownFiles(rootDir, defaultRoot)) {
1707
- diagnostics.push({
1708
- category: "config",
1709
- severity: "review",
1710
- message: `Configured hierarchy no longer includes ${defaultRoot}, but markdown still exists there. Perform manual migration before relying on the new hierarchy.`,
1711
- file: ".truthmark/config.yml"
1712
- });
1713
- }
1714
- }
1715
- return diagnostics;
1716
- };
1717
1740
 
1718
1741
  // src/truth/evidence.ts
1719
1742
  var renderClaimEvidenceCheckedSection = (items) => {
@@ -1758,12 +1781,12 @@ var REPOSITORY_INTELLIGENCE_INSTRUCTIONS = [
1758
1781
  "If unavailable, inspect any present Truthmark config, route files, source files, truth docs, and tests directly, then report that repository-intelligence artifacts were not generated."
1759
1782
  ].join("\n");
1760
1783
  var FEATURE_DOC_TEMPLATE_INSTRUCTIONS = [
1761
- "When creating or updating a truth doc, inspect the routed truth kind and use the matching `docs/templates/<kind>-doc.md` template.",
1784
+ "When creating or updating a truth doc, inspect the routed truth kind and use the matching template under the configured Truthmark templates root.",
1762
1785
  "Supported kinds: behavior, contract, architecture, workflow, operations, and test-behavior.",
1763
1786
  "Treat the HTML comments under each template section as normative authoring guidance for that section.",
1764
1787
  "Align existing docs to that template and write or repair section content so it satisfies the comment guidance while preserving accurate authored content.",
1765
1788
  "If the template is missing, use Scope, Product Decisions, Rationale, and the kind-specific current-truth section.",
1766
- "Teams may edit the template files under docs/templates/ to define their local truth-doc standards."
1789
+ "Teams may edit template files under the configured Truthmark templates root to define their local truth-doc standards."
1767
1790
  ].join("\n");
1768
1791
  var renderTruthDocOwnershipGateSection = (subject, outcome) => {
1769
1792
  return [
@@ -1937,8 +1960,8 @@ var renderHierarchySummary = (config) => {
1937
1960
  return [
1938
1961
  "Truthmark hierarchy hints:",
1939
1962
  "- 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`,
1963
+ `- Root route index, when present: ${config.truthmark.paths.routesIndex}`,
1964
+ `- Area route files, when present: ${config.truthmark.paths.routeAreasRoot}/**/*.md`,
1942
1965
  `- Truth docs, when present: ${truthRoot3}/**/*.md`
1943
1966
  ].join("\n");
1944
1967
  };
@@ -1955,10 +1978,10 @@ var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
1955
1978
  var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
1956
1979
  var renderCompactHierarchySummary = (config) => {
1957
1980
  const truthRoot3 = resolveTruthDocsRoot(config);
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.`;
1981
+ return `Hierarchy hints: config .truthmark/config.yml when present; routes ${config.truthmark.paths.routesIndex} and ${config.truthmark.paths.routeAreasRoot}/**/*.md when present; Truth docs: ${truthRoot3}/**/*.md when present.`;
1959
1982
  };
1960
1983
  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;
1984
+ const portalLine = config.truthmark.generated.portal.enabled ? `Truthmark Portal is a separate manual-only presentation workflow. Run it only when explicitly requested; it writes generated non-canonical static files under ${config.truthmark.paths.portalOutput}/. Markdown remains canonical.` : null;
1962
1985
  return [
1963
1986
  TRUTHMARK_BLOCK_START,
1964
1987
  "## Truthmark Workflow",
@@ -1978,70 +2001,6 @@ var renderAgentsBlock = (config = defaultAgentConfig()) => {
1978
2001
  ].join("\n");
1979
2002
  };
1980
2003
 
1981
- // src/templates/default-standards.ts
1982
- var DEFAULT_STANDARDS = [
1983
- {
1984
- path: "docs/standards/default-principles.md",
1985
- content: `---
1986
- status: active
1987
- doc_type: standard
1988
- last_reviewed: 2026-05-03
1989
- source_of_truth:
1990
- - README.md
1991
- ---
1992
-
1993
- # Default Principles
1994
-
1995
- ## Scope
1996
-
1997
- This is a bootstrap standards baseline for repositories that adopt Truthmark.
1998
-
1999
- ## Reusable Defaults
2000
-
2001
- - Authority order should be explicit.
2002
- - Committed repository artifacts are the durable source of truth.
2003
- - Each document should have one primary responsibility.
2004
- - Each class of fact should have one canonical source.
2005
- - Architecture docs describe system structure, module boundaries, runtime topology, persistence boundaries, cross-cutting contracts, and generated-surface ownership.
2006
- - Do not put ordinary feature behavior in architecture docs.
2007
- - Verification should be explicit, and skipped checks should state why.
2008
- - Missing, stale, broad, overloaded, or unrouteable documentation topology should be repaired through AI-native structure workflow before agents create more generic truth docs.
2009
- - Installed repository workflows should remain usable from committed files even when the Truthmark CLI is unavailable.
2010
- `
2011
- },
2012
- {
2013
- path: "docs/standards/documentation-governance.md",
2014
- content: `---
2015
- status: active
2016
- doc_type: standard
2017
- last_reviewed: 2026-05-03
2018
- source_of_truth:
2019
- - README.md
2020
- ---
2021
-
2022
- # Documentation Governance
2023
-
2024
- ## Core Rules
2025
-
2026
- - Each document should have one primary responsibility.
2027
- - Each class of fact should have one canonical source.
2028
- - Current implementation, reusable standards, and future proposals should be stored separately.
2029
- - Generated helper output is never canonical truth.
2030
- - Architecture docs describe structure and ownership; truth docs describe current product behavior.
2031
-
2032
- ## Truthmark Implications
2033
-
2034
- - Truth Sync should extend mapped docs first, create an area-local doc second, and create a new area only as a last resort.
2035
- - Weak routing produces weak truth maintenance.
2036
- - Missing, stale, broad, overloaded, or unrouteable routing should trigger Truth Structure before more generic truth docs are created.
2037
- `
2038
- }
2039
- ];
2040
- var renderDefaultStandards = (documents) => {
2041
- const existingPaths = new Set(documents.map((document) => document.path));
2042
- return DEFAULT_STANDARDS.filter((template) => !existingPaths.has(template.path));
2043
- };
2044
-
2045
2004
  // src/templates/workflow-surfaces.ts
2046
2005
  import { stringify as stringify2 } from "yaml";
2047
2006
 
@@ -2325,7 +2284,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2325
2284
  "generate the Truthmark Portal",
2326
2285
  "refresh the committed HTML docs site",
2327
2286
  "create a browsable project map from Truthmark docs",
2328
- "update docs/truthmark-portal",
2287
+ "update the Truthmark Portal output",
2329
2288
  "make a human-readable static site from the truth docs"
2330
2289
  ],
2331
2290
  negativeTriggers: [
@@ -2339,7 +2298,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2339
2298
  forbiddenAdjacency: [
2340
2299
  "must not run as a completion gate",
2341
2300
  "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"
2301
+ "must not write outside the fixed Portal output directory"
2343
2302
  ],
2344
2303
  requiredGates: [
2345
2304
  "manual-only invocation",
@@ -2347,7 +2306,7 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
2347
2306
  "Markdown canonical statement",
2348
2307
  "source provenance"
2349
2308
  ],
2350
- allowedWrites: ["configured Portal output directory only"],
2309
+ allowedWrites: ["fixed Portal output directory only"],
2351
2310
  reportSections: [
2352
2311
  "Output path",
2353
2312
  "Page count",
@@ -2372,7 +2331,7 @@ var renderMarkdownExample = (content) => {
2372
2331
  };
2373
2332
  var TRUTH_CHECK_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-check; Codex /truthmark-check or $truthmark-check; Claude Code /truthmark-check; GitHub Copilot /truthmark-check; Gemini CLI /truthmark:check.";
2374
2333
  var renderTruthCheckReportExample = (config = defaultAgentConfig()) => {
2375
- const rootRouteIndex = config.docs.routing.rootIndex;
2334
+ const rootRouteIndex = config.truthmark.paths.routesIndex;
2376
2335
  return `Truth Check: completed
2377
2336
 
2378
2337
  Files reviewed:
@@ -2441,7 +2400,7 @@ Truth Check is agent-led:
2441
2400
 
2442
2401
  - inspect .truthmark/config.yml and configured route files only when they exist; then inspect canonical docs and relevant implementation directly
2443
2402
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
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
2403
+ - inspect the configured root route index at ${config.truthmark.paths.routesIndex} and relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ when they exist
2445
2404
  - check that current docs describe current code rather than historical plans
2446
2405
  - check that route files map code surfaces to canonical truth docs when route files exist
2447
2406
  - check for broad, catch-all, index-like, or mixed-owner truth docs and report them as topology issues requiring Truth Structure
@@ -2474,7 +2433,7 @@ Implementation reviewed:
2474
2433
  - src/routing/area-resolver.ts
2475
2434
 
2476
2435
  Ownership reviewed:
2477
- - ${config.docs.routing.rootIndex}
2436
+ - ${config.truthmark.paths.routesIndex}
2478
2437
 
2479
2438
  Truth docs created:
2480
2439
  - ${truthDocsRoot}/contracts.md
@@ -2486,14 +2445,14 @@ Truth docs restructured:
2486
2445
  - ${truthDocsRoot}/check-diagnostics.md
2487
2446
 
2488
2447
  Routing updated:
2489
- - ${config.docs.routing.rootIndex}
2448
+ - ${config.truthmark.paths.routesIndex}
2490
2449
 
2491
2450
  ${renderClaimEvidenceCheckedSection([
2492
2451
  {
2493
2452
  claim: "Route resolution behavior is documented in the contracts truth doc.",
2494
2453
  evidence: [
2495
2454
  "src/routing/area-resolver.ts:14",
2496
- `${config.docs.routing.rootIndex}:9`
2455
+ `${config.truthmark.paths.routesIndex}:9`
2497
2456
  ],
2498
2457
  result: "supported"
2499
2458
  }
@@ -2551,7 +2510,7 @@ Truth Document is manual and implementation-first:
2551
2510
  - inspect .truthmark/config.yml and configured route files only when they exist; then inspect existing canonical docs, implementation code, and tests directly
2552
2511
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2553
2512
  - document current implemented behavior; do not invent future behavior or planned endpoints
2554
- - may write canonical truth docs and ${config.docs.routing.rootIndex} or relevant child route files only
2513
+ - may write canonical truth docs and ${config.truthmark.paths.routesIndex} or relevant child route files only
2555
2514
  - must not write functional code
2556
2515
  - when routing is missing, stale, broad, overloaded, catch-all, or cannot map the behavior to a bounded truth owner, run Truth Structure first when routing repair is safe and in scope
2557
2516
  - block and recommend Truth Structure when routing repair is unsafe, ambiguous, or outside the task boundary
@@ -2614,7 +2573,7 @@ Why this workflow:
2614
2573
  - forbidden adjacency considered: must not edit functional code
2615
2574
 
2616
2575
  Likely route owner:
2617
- - route file: ${config.docs.routing.rootIndex}
2576
+ - route file: ${config.truthmark.paths.routesIndex}
2618
2577
  - truth doc: ${truthDocsRoot}/example.md
2619
2578
  - confidence: medium
2620
2579
 
@@ -2658,8 +2617,8 @@ Purpose:
2658
2617
 
2659
2618
  Read:
2660
2619
  - .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
2620
+ - ${config.truthmark.paths.routesIndex}, only when present
2621
+ - relevant child route files under ${config.truthmark.paths.routeAreasRoot}/, only when present
2663
2622
  - relevant truth docs and implementation files needed to preview ownership
2664
2623
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
2665
2624
 
@@ -2688,12 +2647,12 @@ ${renderMarkdownExample3(renderTruthPreviewReportExample(config))}`;
2688
2647
  var TRUTHMARK_PORTAL_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-portal; Codex /truthmark-portal or $truthmark-portal; Claude Code /truthmark-portal; GitHub Copilot /truthmark-portal; Gemini CLI /truthmark:portal.";
2689
2648
  var renderTruthmarkPortalSkillBody = (config = defaultAgentConfig()) => {
2690
2649
  const workflow = getTruthmarkWorkflow("truthmark-portal");
2691
- const output = config.truthmarkPortal.output;
2692
- const template = config.truthmarkPortal.template;
2650
+ const output = config.truthmark.paths.portalOutput;
2651
+ const template = config.truthmark.paths.portalTemplate;
2693
2652
  return `---
2694
2653
  name: truthmark-portal
2695
2654
  description: ${workflow.description}
2696
- argument-hint: Optional output path, template, or portal generation focus
2655
+ argument-hint: Optional portal generation focus
2697
2656
  user-invocable: true
2698
2657
  truthmark-version: ${TRUTHMARK_VERSION}
2699
2658
  ---
@@ -2709,9 +2668,9 @@ Core rules:
2709
2668
  - Markdown remains canonical; generated HTML is presentation only.
2710
2669
  - Read Markdown directly from the checkout; the workflow does not require the truthmark CLI or package.
2711
2670
  - 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.
2671
+ - Determined Portal output is ${output}.
2672
+ - Determined Portal template path is ${template}; use built-in template instructions if that file is absent.
2673
+ - The workflow may replace the entire output directory, but writes are limited to the fixed Portal output directory only.
2715
2674
  - Portal writes are generated non-canonical static files for human browsing.
2716
2675
  - Generate a committed multi-page static HTML site with local CSS, JavaScript, assets, and search metadata under the output directory.
2717
2676
  - Use no remote dependencies by default: no remote scripts, analytics, fonts, CSS, or CDN assets.
@@ -2723,8 +2682,8 @@ Core rules:
2723
2682
  Workflow:
2724
2683
 
2725
2684
  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.
2685
+ 2. Inspect .truthmark/config.yml and configured route docs only when they exist; read repository instruction files when present, truth docs, architecture docs, standards docs, and the determined Portal template when present.
2686
+ 3. Validate the determined output path is repo-relative, non-empty, inside the repository, and does not overlap canonical docs, source roots, routing files, or instruction targets.
2728
2687
  4. Plan the generated page inventory, diagrams/assets, source docs reviewed, and skipped or ambiguous docs.
2729
2688
  5. Replace or write only under ${output}; do not edit canonical Markdown, routing, source code, or instruction files unless the user explicitly changes scope.
2730
2689
  6. Generate the multi-page static site with local assets/search metadata and visible source provenance.
@@ -2771,11 +2730,11 @@ var renderTruthStructureReportExample = (config = defaultAgentConfig()) => {
2771
2730
  Topology reviewed:
2772
2731
  - controllers: src/auth/**
2773
2732
  - docs root: ${truthDocsRoot}
2774
- - route files: ${config.docs.routing.rootIndex}
2733
+ - route files: ${config.truthmark.paths.routesIndex}
2775
2734
  Areas reviewed:
2776
2735
  - src/auth/**
2777
2736
  Routing updated:
2778
- - ${config.docs.routing.rootIndex}
2737
+ - ${config.truthmark.paths.routesIndex}
2779
2738
  Initial truth boundary:
2780
2739
  - Area: Authentication
2781
2740
  - Code: src/auth/**
@@ -2790,7 +2749,7 @@ Truth docs restructured:
2790
2749
  ${renderClaimEvidenceCheckedSection([
2791
2750
  {
2792
2751
  claim: "Session behavior belongs to a dedicated Authentication truth owner.",
2793
- evidence: ["src/auth/**", `${config.docs.routing.rootIndex}:7`],
2752
+ evidence: ["src/auth/**", `${config.truthmark.paths.routesIndex}:7`],
2794
2753
  result: "supported"
2795
2754
  }
2796
2755
  ])}
@@ -2826,15 +2785,15 @@ Invocations: ${TRUTH_STRUCTURE_EXPLICIT_INVOCATIONS}
2826
2785
  Truth Structure is agent-native:
2827
2786
  - inspect repository layout, current docs, Truthmark config and route files when present, and relevant code directly
2828
2787
  - ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
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
2788
+ - inspect the configured root route index at ${config.truthmark.paths.routesIndex} and relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ when they exist
2830
2789
  - define areas by product or behavior ownership, not by mechanical directory mirroring
2831
- - create or repair ${config.docs.routing.rootIndex}
2790
+ - create or repair ${config.truthmark.paths.routesIndex}
2832
2791
  - create starter truth docs when useful and when they belong in the canonical current-truth surface
2833
2792
  - Starter truth docs must use closed YAML frontmatter bounded by opening and closing --- lines; include status, doc_type, last_reviewed, and source_of_truth inside that frontmatter.
2834
2793
  - Starter truth docs must include ## Product Decisions and ## Rationale sections.
2835
2794
  ${subagentMode}
2836
2795
  ${FEATURE_DOC_TEMPLATE_INSTRUCTIONS}
2837
- - use ${truthDocsRoot}/**, docs/architecture/**, or docs/standards/** for current truth destinations
2796
+ - use ${truthDocsRoot}/** for current truth destinations
2838
2797
  - use only canonical current-truth destinations for starter truth docs
2839
2798
  - keep active Product Decisions and Rationale in the canonical doc that owns the behavior
2840
2799
  - preserve unrelated authored content
@@ -2869,7 +2828,7 @@ Topology pressure signals:
2869
2828
  - the configured truth root has many direct non-index docs
2870
2829
  - a changed controller, route, or service cannot map to a specific behavior doc
2871
2830
  - Truth Sync would need to create a new generic truth doc because routing is too broad
2872
- - endpoint or controller names reveal domains missing from ${config.docs.routing.areaFilesRoot}/**
2831
+ - endpoint or controller names reveal domains missing from ${config.truthmark.paths.routeAreasRoot}/**
2873
2832
  Use these review thresholds as guidance:
2874
2833
  - more than 10 direct truth docs in one folder
2875
2834
  - more than 15 leaf areas in one child route file
@@ -2878,7 +2837,7 @@ Use these review thresholds as guidance:
2878
2837
  Repair rules:
2879
2838
  - split broad, overloaded, or catch-all areas into behavior-owned child route files
2880
2839
  - split mixed-owner truth docs into bounded owner docs before adding new behavior claims
2881
- - create route files under ${config.docs.routing.areaFilesRoot}/ when a product/domain boundary is clear
2840
+ - create route files under ${config.truthmark.paths.routeAreasRoot}/ when a product/domain boundary is clear
2882
2841
  - create behavior truth docs under the configured truth root only when behavior lacks a current doc
2883
2842
  - README.md files are indexes, not Truth Sync targets
2884
2843
  - prefer bounded leaf truth docs at <truth-root>/<domain>/<behavior>.md
@@ -3117,12 +3076,12 @@ Report completion in this shape:
3117
3076
  ${renderMarkdownExample5(
3118
3077
  renderTruthSyncCompletedReport({
3119
3078
  changedCode: ["src/auth/session.ts"],
3120
- ownershipReviewed: [config.docs.routing.rootIndex],
3079
+ ownershipReviewed: [config.truthmark.paths.routesIndex],
3121
3080
  truthDocsUpdated: [`${truthDocsRoot}/repository/overview.md`],
3122
3081
  evidenceChecked: [
3123
3082
  {
3124
3083
  claim: "Session timeout behavior is documented in the mapped repository truth doc.",
3125
- evidence: ["src/auth/session.ts:12", `${config.docs.routing.rootIndex}:11`],
3084
+ evidence: ["src/auth/session.ts:12", `${config.truthmark.paths.routesIndex}:11`],
3126
3085
  result: "supported"
3127
3086
  }
3128
3087
  ],
@@ -3134,7 +3093,7 @@ Blocked report example:
3134
3093
  ${renderMarkdownExample5(
3135
3094
  renderTruthSyncBlockedReport({
3136
3095
  reason: "routing repair is not allowed",
3137
- manualReviewFiles: [config.docs.routing.rootIndex],
3096
+ manualReviewFiles: [config.truthmark.paths.routesIndex],
3138
3097
  nextAction: "update routing metadata and rerun Truth Sync"
3139
3098
  })
3140
3099
  )}`;
@@ -3157,20 +3116,20 @@ var TRUTHMARK_WRITE_WORKER_REPORT_FIELDS = [
3157
3116
  ];
3158
3117
 
3159
3118
  // src/templates/workflow-surfaces.ts
3160
- var TRUTHMARK_STRUCTURE_SKILL_PATH = ".codex/skills/truthmark-structure/SKILL.md";
3161
- var TRUTHMARK_STRUCTURE_SKILL_METADATA_PATH = ".codex/skills/truthmark-structure/agents/openai.yaml";
3162
- var TRUTHMARK_DOCUMENT_SKILL_PATH = ".codex/skills/truthmark-document/SKILL.md";
3163
- var TRUTHMARK_DOCUMENT_SKILL_METADATA_PATH = ".codex/skills/truthmark-document/agents/openai.yaml";
3164
- var TRUTHMARK_SYNC_SKILL_PATH = ".codex/skills/truthmark-sync/SKILL.md";
3165
- var TRUTHMARK_SYNC_SKILL_METADATA_PATH = ".codex/skills/truthmark-sync/agents/openai.yaml";
3166
- var TRUTHMARK_REALIZE_SKILL_PATH = ".codex/skills/truthmark-realize/SKILL.md";
3167
- var TRUTHMARK_REALIZE_SKILL_METADATA_PATH = ".codex/skills/truthmark-realize/agents/openai.yaml";
3168
- var TRUTHMARK_CHECK_SKILL_PATH = ".codex/skills/truthmark-check/SKILL.md";
3169
- var TRUTHMARK_CHECK_SKILL_METADATA_PATH = ".codex/skills/truthmark-check/agents/openai.yaml";
3170
- var TRUTHMARK_PREVIEW_SKILL_PATH = ".codex/skills/truthmark-preview/SKILL.md";
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";
3119
+ var TRUTHMARK_STRUCTURE_SKILL_PATH = ".agents/skills/truthmark-structure/SKILL.md";
3120
+ var TRUTHMARK_STRUCTURE_SKILL_METADATA_PATH = ".agents/skills/truthmark-structure/agents/openai.yaml";
3121
+ var TRUTHMARK_DOCUMENT_SKILL_PATH = ".agents/skills/truthmark-document/SKILL.md";
3122
+ var TRUTHMARK_DOCUMENT_SKILL_METADATA_PATH = ".agents/skills/truthmark-document/agents/openai.yaml";
3123
+ var TRUTHMARK_SYNC_SKILL_PATH = ".agents/skills/truthmark-sync/SKILL.md";
3124
+ var TRUTHMARK_SYNC_SKILL_METADATA_PATH = ".agents/skills/truthmark-sync/agents/openai.yaml";
3125
+ var TRUTHMARK_REALIZE_SKILL_PATH = ".agents/skills/truthmark-realize/SKILL.md";
3126
+ var TRUTHMARK_REALIZE_SKILL_METADATA_PATH = ".agents/skills/truthmark-realize/agents/openai.yaml";
3127
+ var TRUTHMARK_CHECK_SKILL_PATH = ".agents/skills/truthmark-check/SKILL.md";
3128
+ var TRUTHMARK_CHECK_SKILL_METADATA_PATH = ".agents/skills/truthmark-check/agents/openai.yaml";
3129
+ var TRUTHMARK_PREVIEW_SKILL_PATH = ".agents/skills/truthmark-preview/SKILL.md";
3130
+ var TRUTHMARK_PREVIEW_SKILL_METADATA_PATH = ".agents/skills/truthmark-preview/agents/openai.yaml";
3131
+ var TRUTHMARK_PORTAL_SKILL_PATH = ".agents/skills/truthmark-portal/SKILL.md";
3132
+ var TRUTHMARK_PORTAL_SKILL_METADATA_PATH = ".agents/skills/truthmark-portal/agents/openai.yaml";
3174
3133
  var TRUTHMARK_ROUTE_AUDITOR_AGENT_PATH = ".codex/agents/truth-route-auditor.toml";
3175
3134
  var TRUTHMARK_CLAIM_VERIFIER_AGENT_PATH = ".codex/agents/truth-claim-verifier.toml";
3176
3135
  var TRUTHMARK_DOC_REVIEWER_AGENT_PATH = ".codex/agents/truth-doc-reviewer.toml";
@@ -3201,10 +3160,10 @@ var TRUTHMARK_COPILOT_REALIZE_PROMPT_PATH = ".github/prompts/truthmark-realize.p
3201
3160
  var TRUTHMARK_COPILOT_CHECK_PROMPT_PATH = ".github/prompts/truthmark-check.prompt.md";
3202
3161
  var TRUTHMARK_COPILOT_PREVIEW_PROMPT_PATH = ".github/prompts/truthmark-preview.prompt.md";
3203
3162
  var TRUTHMARK_COPILOT_PORTAL_PROMPT_PATH = ".github/prompts/truthmark-portal.prompt.md";
3204
- var TRUTHMARK_COPILOT_ROUTE_AUDITOR_AGENT_PATH = ".github/agents/truth-route-auditor.agent.md";
3205
- var TRUTHMARK_COPILOT_CLAIM_VERIFIER_AGENT_PATH = ".github/agents/truth-claim-verifier.agent.md";
3206
- var TRUTHMARK_COPILOT_DOC_REVIEWER_AGENT_PATH = ".github/agents/truth-doc-reviewer.agent.md";
3207
- var TRUTHMARK_COPILOT_DOC_WRITER_AGENT_PATH = ".github/agents/truth-doc-writer.agent.md";
3163
+ var TRUTHMARK_COPILOT_ROUTE_AUDITOR_AGENT_PATH = ".github/agents/truth-route-auditor.md";
3164
+ var TRUTHMARK_COPILOT_CLAIM_VERIFIER_AGENT_PATH = ".github/agents/truth-claim-verifier.md";
3165
+ var TRUTHMARK_COPILOT_DOC_REVIEWER_AGENT_PATH = ".github/agents/truth-doc-reviewer.md";
3166
+ var TRUTHMARK_COPILOT_DOC_WRITER_AGENT_PATH = ".github/agents/truth-doc-writer.md";
3208
3167
  var renderGeminiCommand = (description, prompt) => {
3209
3168
  const promptWithArgs = `${prompt.trimEnd()}
3210
3169
  User focus or arguments: {{args}}`;
@@ -3230,7 +3189,7 @@ var renderTomlStringArray = (values) => {
3230
3189
  return `[${values.map(renderTomlString).join(", ")}]`;
3231
3190
  };
3232
3191
  var TRUTH_REALIZE_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-realize; Codex /truthmark-realize or $truthmark-realize; Claude Code /truthmark-realize; GitHub Copilot /truthmark-realize; Gemini CLI /truthmark:realize.";
3233
- var routeFilesHint = (config) => `${config.docs.routing.rootIndex}; ${config.docs.routing.areaFilesRoot}/`;
3192
+ var routeFilesHint = (config) => `${config.truthmark.paths.routesIndex}; ${config.truthmark.paths.routeAreasRoot}/`;
3234
3193
  var WORKFLOW_PACKAGE_DEFINITIONS = {
3235
3194
  "truthmark-structure": {
3236
3195
  title: "Truthmark Structure",
@@ -3258,7 +3217,7 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3258
3217
  "Document current implemented behavior; do not invent future behavior.",
3259
3218
  "May write canonical truth docs and truth routing files only; must not write functional code.",
3260
3219
  "Read support/procedure.md before editing truth docs.",
3261
- "Read support/subagents-and-leases.md before dispatching or accepting worker output.",
3220
+ "Read support/subagents-and-leases.md only when dispatching or accepting worker output.",
3262
3221
  "Read support/report-template.md before the final report."
3263
3222
  ],
3264
3223
  parentRule: "Parent agent owns Truth Document acceptance, lease validation, and final report"
@@ -3275,7 +3234,7 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3275
3234
  "direct checkout inspection is the canonical path; do not require the truthmark binary.",
3276
3235
  "May write canonical truth docs and truth routing files only; must not rewrite functional code.",
3277
3236
  "Read support/procedure.md before editing truth docs.",
3278
- "Read support/subagents-and-leases.md before dispatching or accepting worker output.",
3237
+ "Read support/subagents-and-leases.md only when dispatching or accepting worker output.",
3279
3238
  "Read support/report-template.md before the final report."
3280
3239
  ],
3281
3240
  parentRule: "Parent agent owns Truth Sync acceptance, lease validation, and final report"
@@ -3318,16 +3277,15 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3318
3277
  "Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
3319
3278
  `Inspect .truthmark/config.yml and configured route files (${routeFilesHint(config)}) only when they exist; then inspect canonical docs and relevant implementation directly.`,
3320
3279
  "Report issues and suggested fixes; do not silently rewrite unrelated files.",
3321
- "Direct checkout inspection is valid even when local tooling is unavailable.",
3322
3280
  "Read support/procedure.md before auditing details.",
3323
- "Read support/subagents-and-leases.md before dispatching verifier subagents.",
3281
+ "Read support/subagents-and-leases.md only when dispatching verifier subagents.",
3324
3282
  "Read support/report-template.md before the final report."
3325
3283
  ],
3326
3284
  parentRule: "Parent agent owns the final Truth Check report"
3327
3285
  },
3328
3286
  "truthmark-portal": {
3329
3287
  title: "Truthmark Portal",
3330
- argumentHint: "Optional output path, template, or portal generation focus",
3288
+ argumentHint: "Optional portal generation focus",
3331
3289
  invocations: TRUTHMARK_PORTAL_EXPLICIT_INVOCATIONS,
3332
3290
  use: () => "Use this skill only when the user explicitly asks to generate or refresh the committed static HTML Truthmark Portal.",
3333
3291
  quickRules: (config) => [
@@ -3336,8 +3294,8 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
3336
3294
  "Markdown remains canonical; generated HTML is non-canonical presentation only.",
3337
3295
  "Read Markdown directly; the workflow does not require the truthmark CLI or package.",
3338
3296
  "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.`,
3297
+ `Write only under fixed Portal output ${config.truthmark.paths.portalOutput}.`,
3298
+ `Use determined Portal template ${config.truthmark.paths.portalTemplate} when present; no .truthmark/index.json dependency.`,
3341
3299
  "Use no remote dependencies by default and include source provenance on every page.",
3342
3300
  "Read support/procedure.md before generating Portal output.",
3343
3301
  "Read support/report-template.md before the final report."
@@ -3441,7 +3399,25 @@ var renderStandaloneWorkflowSkillBody = (workflowId, config) => {
3441
3399
  var renderWorkflowEntrypoint = (workflowId, config, supportFiles, host) => {
3442
3400
  const workflow = getTruthmarkWorkflow(workflowId);
3443
3401
  const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
3444
- const supportFileList = supportFiles.map((supportFile) => `- ${supportFile}`).join("\n");
3402
+ const supportFileUsage = (supportFile) => {
3403
+ if (supportFile === "support/procedure.md") {
3404
+ return "read before edits or detailed auditing; contains core quality gates";
3405
+ }
3406
+ if (supportFile === "support/report-template.md") {
3407
+ return "read before the final report";
3408
+ }
3409
+ if (supportFile === "support/subagents-and-leases.md") {
3410
+ return "read only when using subagents, leases, or accepting worker output";
3411
+ }
3412
+ if (supportFile === "support/helper-policy.md") {
3413
+ return "read only when invoking helper validators or reporting helper status";
3414
+ }
3415
+ if (supportFile === "helper-manifest.yml") {
3416
+ return "read only when invoking helper validators or validating helper registration";
3417
+ }
3418
+ return "available when relevant to the current step";
3419
+ };
3420
+ const supportFileList = supportFiles.map((supportFile) => `- ${supportFile} \u2014 ${supportFileUsage(supportFile)}`).join("\n");
3445
3421
  const hostUsage = host === "github-copilot" ? "Use as a Copilot agent skill. Prompt files remain available under `.github/prompts/` for command-style invocation in supported Copilot IDEs." : host === "gemini-cli" ? "Use as a Gemini CLI Agent Skill; commands remain available under `/truthmark:*` for command-first invocation." : void 0;
3446
3422
  return `---
3447
3423
  name: ${workflowId}
@@ -3453,10 +3429,9 @@ truthmark-version: ${TRUTHMARK_VERSION}
3453
3429
 
3454
3430
  # ${definition.title}
3455
3431
 
3456
- ${definition.use(config)}
3457
- ${hostUsage === void 0 ? "" : `
3458
- ${hostUsage}
3459
- `}
3432
+ ${definition.use(config)}${hostUsage === void 0 ? "" : `
3433
+
3434
+ ${hostUsage}`}
3460
3435
 
3461
3436
  Invocations: ${definition.invocations}
3462
3437
 
@@ -3586,10 +3561,10 @@ var renderOpenCodeWriterEditAllowRules = (config) => {
3586
3561
  resolveTruthDocsRoot(config)
3587
3562
  );
3588
3563
  const rootRouteIndex = normalizeOpenCodePermissionPath(
3589
- config.docs.routing.rootIndex
3564
+ config.truthmark.paths.routesIndex
3590
3565
  );
3591
3566
  const areaFilesRoot = normalizeOpenCodePermissionPath(
3592
- config.docs.routing.areaFilesRoot
3567
+ config.truthmark.paths.routeAreasRoot
3593
3568
  );
3594
3569
  const allowedPatterns = [
3595
3570
  appendOpenCodePermissionGlob(truthDocsRoot, "/**"),
@@ -4359,7 +4334,7 @@ var codexFiles = (config) => {
4359
4334
  content: renderTruthmarkDocWriterAgent()
4360
4335
  }
4361
4336
  ];
4362
- if (config.truthmarkPortal.enabled) {
4337
+ if (config.truthmark.generated.portal.enabled) {
4363
4338
  files.push(
4364
4339
  ...renderTruthmarkSkillPackage({
4365
4340
  skillPath: TRUTHMARK_PORTAL_SKILL_PATH,
@@ -4430,7 +4405,7 @@ var opencodeFiles = (config) => {
4430
4405
  content: renderTruthmarkOpenCodeDocWriterAgent(config)
4431
4406
  }
4432
4407
  ];
4433
- if (config.truthmarkPortal.enabled) {
4408
+ if (config.truthmark.generated.portal.enabled) {
4434
4409
  files.push(
4435
4410
  ...renderTruthmarkSkillPackage({
4436
4411
  skillPath: ".opencode/skills/truthmark-portal/SKILL.md",
@@ -4498,7 +4473,7 @@ var claudeFiles = (config, block) => {
4498
4473
  content: renderTruthmarkClaudeDocWriterAgent()
4499
4474
  }
4500
4475
  ];
4501
- if (config.truthmarkPortal.enabled) {
4476
+ if (config.truthmark.generated.portal.enabled) {
4502
4477
  files.push(
4503
4478
  ...renderTruthmarkSkillPackage({
4504
4479
  skillPath: ".claude/skills/truthmark-portal/SKILL.md",
@@ -4590,7 +4565,7 @@ var copilotFiles = (config, block) => {
4590
4565
  content: renderTruthmarkCopilotDocWriterAgent()
4591
4566
  }
4592
4567
  ];
4593
- if (config.truthmarkPortal.enabled) {
4568
+ if (config.truthmark.generated.portal.enabled) {
4594
4569
  files.push(
4595
4570
  ...renderTruthmarkSkillPackage({
4596
4571
  skillPath: ".github/skills/truthmark-portal/SKILL.md",
@@ -4686,7 +4661,7 @@ var geminiFiles = (config, block) => {
4686
4661
  content: renderTruthmarkGeminiDocWriterAgent()
4687
4662
  }
4688
4663
  ];
4689
- if (config.truthmarkPortal.enabled) {
4664
+ if (config.truthmark.generated.portal.enabled) {
4690
4665
  files.push(
4691
4666
  ...renderTruthmarkSkillPackage({
4692
4667
  skillPath: ".gemini/skills/truthmark-portal/SKILL.md",
@@ -4740,19 +4715,10 @@ var escapeRegExp = (value) => {
4740
4715
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4741
4716
  };
4742
4717
  var MANAGED_WORKFLOW_HEADING = "## Truthmark Workflow";
4743
- var LEGACY_MANAGED_LINES = [
4744
- "### Truth Sync",
4745
- "- may read changed functional code files",
4746
- "- may write truth docs only",
4747
- "- must not rewrite functional code"
4748
- ];
4749
- var CANONICAL_MANAGED_LINES = /* @__PURE__ */ new Set(
4750
- [
4751
- ...renderAgentsBlock().split("\n").map((line) => line.trim()).filter(
4752
- (line) => line.length > 0 && line !== TRUTHMARK_BLOCK_START && line !== TRUTHMARK_BLOCK_END
4753
- ),
4754
- ...LEGACY_MANAGED_LINES
4755
- ]
4718
+ var CANONICAL_MANAGED_LINES = new Set(
4719
+ renderAgentsBlock().split("\n").map((line) => line.trim()).filter(
4720
+ (line) => line.length > 0 && line !== TRUTHMARK_BLOCK_START && line !== TRUTHMARK_BLOCK_END
4721
+ )
4756
4722
  );
4757
4723
  var countCanonicalManagedLineMatches = (lines) => {
4758
4724
  return lines.reduce((matchCount, line) => {
@@ -4779,46 +4745,11 @@ var removeTrailingManagedChunk = (preservedLines) => {
4779
4745
  preservedLines.splice(startIndex);
4780
4746
  }
4781
4747
  };
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(" ");
4790
- var normalizeLegacyInstructionPreamble = (content) => {
4791
- return content.replaceAll(
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."
4803
- ).replaceAll("Codex-specific:", "Agent-specific:").replaceAll(
4804
- "- Read `docs/README.md` for the canonical docs map.",
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."
4809
- ).replaceAll(
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."
4815
- );
4816
- };
4817
4748
  var upsertManagedBlock = (existingContent, block) => {
4818
4749
  if (!existingContent || existingContent.trim().length === 0) {
4819
4750
  return block;
4820
4751
  }
4821
- const normalizedExistingContent = normalizeLegacyInstructionPreamble(existingContent);
4752
+ const normalizedExistingContent = existingContent;
4822
4753
  const startMarkerPattern = new RegExp(escapeRegExp(TRUTHMARK_BLOCK_START), "g");
4823
4754
  const endMarkerPattern = new RegExp(escapeRegExp(TRUTHMARK_BLOCK_END), "g");
4824
4755
  const managedBlockPattern = new RegExp(
@@ -4887,13 +4818,13 @@ var diagnosticCategoryForPath = (filePath, config) => {
4887
4818
  if (filePath === "AGENTS.md") {
4888
4819
  return "truth-sync";
4889
4820
  }
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/")) {
4821
+ if (filePath === ".github/prompts/truthmark-realize.prompt.md" || filePath.startsWith(".github/skills/truthmark-realize/") || filePath.startsWith(".claude/skills/truthmark-realize/") || filePath.startsWith(".opencode/skills/truthmark-realize/") || filePath.startsWith(".agents/skills/truthmark-realize/") || filePath.startsWith(".gemini/skills/truthmark-realize/")) {
4891
4822
  return "realization";
4892
4823
  }
4893
4824
  if (filePath === "CLAUDE.md" || filePath === "GEMINI.md" || filePath === ".github/copilot-instructions.md" || filePath.startsWith(".github/prompts/truthmark-") || filePath.startsWith(".github/agents/truth-") || filePath.startsWith(".github/skills/truthmark-") || filePath.startsWith(".claude/agents/truth-") || filePath.startsWith(".claude/skills/truthmark-") || filePath.startsWith(".opencode/skills/truthmark-") || filePath.startsWith(".opencode/agents/") || filePath.startsWith(".codex/agents/") || filePath.startsWith(".gemini/agents/truth-") || filePath.startsWith(".gemini/skills/truthmark-")) {
4894
4825
  return "truth-sync";
4895
4826
  }
4896
- if (filePath.startsWith(".codex/skills/truthmark-")) {
4827
+ if (filePath.startsWith(".agents/skills/truthmark-")) {
4897
4828
  return "truth-sync";
4898
4829
  }
4899
4830
  if (filePath.startsWith(".gemini/commands/truthmark/realize")) {
@@ -4902,8 +4833,8 @@ var diagnosticCategoryForPath = (filePath, config) => {
4902
4833
  if (filePath.startsWith(".gemini/commands/truthmark/")) {
4903
4834
  return "truth-sync";
4904
4835
  }
4905
- if (filePath === config.docs.routing.rootIndex) {
4906
- return "authority";
4836
+ if (filePath === config.truthmark.paths.routesIndex) {
4837
+ return "area-index";
4907
4838
  }
4908
4839
  return "config";
4909
4840
  };
@@ -4938,7 +4869,7 @@ var runInit = async (cwd) => {
4938
4869
  if (!loadedConfig.config) {
4939
4870
  return {
4940
4871
  command: "init",
4941
- summary: "Truthmark init requires .truthmark/config.yml. Run truthmark config first, review the hierarchy, then run truthmark init.",
4872
+ summary: "Truthmark init requires .truthmark/config.yml. Run truthmark config first, review the workspace paths, then run truthmark init.",
4942
4873
  diagnostics: loadedConfig.diagnostics,
4943
4874
  data: {
4944
4875
  repositoryRoot: repository.repositoryRoot,
@@ -4949,14 +4880,9 @@ var runInit = async (cwd) => {
4949
4880
  }
4950
4881
  };
4951
4882
  }
4952
- const defaultStandards = renderDefaultStandards([]);
4953
4883
  const results = [];
4954
- for (const template of defaultStandards) {
4955
- results.push(await ensureRepoFile(rootDir, template.path, template.content));
4956
- }
4957
4884
  const config = loadedConfig.config;
4958
4885
  results.push(...await scaffoldHierarchy(rootDir, config));
4959
- const migrationDiagnostics = await detectHierarchyMigrationDiagnostics(rootDir, config);
4960
4886
  const block = renderAgentsBlock(config);
4961
4887
  const platformFiles = renderGeneratedSurfaces(config, block);
4962
4888
  for (const file of platformFiles) {
@@ -4966,7 +4892,7 @@ var runInit = async (cwd) => {
4966
4892
  return {
4967
4893
  command: "init",
4968
4894
  summary: changedResults.length > 0 ? "Initialized or updated the Truthmark repository scaffold." : "Truthmark repository scaffold is already up to date.",
4969
- diagnostics: [...writeDiagnostics(results, config), ...migrationDiagnostics],
4895
+ diagnostics: writeDiagnostics(results, config),
4970
4896
  data: {
4971
4897
  repositoryRoot: repository.repositoryRoot,
4972
4898
  worktreePath: repository.worktreePath,
@@ -4979,7 +4905,7 @@ var runInit = async (cwd) => {
4979
4905
 
4980
4906
  // src/checks/branch-scope.ts
4981
4907
  import fs8 from "fs/promises";
4982
- import fg2 from "fast-glob";
4908
+ import fg from "fast-glob";
4983
4909
 
4984
4910
  // src/markdown/hash.ts
4985
4911
  import { createHash } from "crypto";
@@ -5020,10 +4946,11 @@ var getBranchScopeData = async (cwd) => {
5020
4946
  const repository = await getGitRepository(cwd);
5021
4947
  const relevantFileHashes = {};
5022
4948
  const loadResult = await loadConfig(repository.worktreePath);
5023
- const rootIndex = loadResult.config?.docs.routing.rootIndex ?? DEFAULT_DOCS_HIERARCHY.routing.root_index;
5024
- const areaFilesRoot = loadResult.config?.docs.routing.areaFilesRoot ?? DEFAULT_DOCS_HIERARCHY.routing.area_files_root;
4949
+ const defaultConfig = createDefaultConfig();
4950
+ const rootIndex = loadResult.config?.truthmark.paths.routesIndex ?? defaultConfig.truthmark.paths.routesIndex;
4951
+ const areaFilesRoot = loadResult.config?.truthmark.paths.routeAreasRoot ?? defaultConfig.truthmark.paths.routeAreasRoot;
5025
4952
  const relevantFiles = /* @__PURE__ */ new Set([...RELEVANT_BRANCH_SCOPE_FILES, rootIndex]);
5026
- const routeFiles = await fg2([`${areaFilesRoot}/**/*.md`], {
4953
+ const routeFiles = await fg([`${areaFilesRoot}/**/*.md`], {
5027
4954
  cwd: repository.worktreePath,
5028
4955
  onlyFiles: true,
5029
4956
  followSymbolicLinks: false
@@ -5051,7 +4978,7 @@ var getBranchScopeData = async (cwd) => {
5051
4978
 
5052
4979
  // src/checks/authority.ts
5053
4980
  import fs9 from "fs/promises";
5054
- import fg3 from "fast-glob";
4981
+ import fg2 from "fast-glob";
5055
4982
  var looksLikeGlob = (pattern) => {
5056
4983
  return /[*?[\]{}()!+@]/u.test(pattern);
5057
4984
  };
@@ -5066,11 +4993,11 @@ var pathExists = async (absolutePath) => {
5066
4993
  throw error;
5067
4994
  }
5068
4995
  };
5069
- var checkAuthority = async (rootDir, config) => {
4996
+ var checkControlledPaths = async (rootDir, controlledPaths) => {
5070
4997
  const diagnostics = [];
5071
4998
  const orderedPaths = [];
5072
4999
  const seenPaths = /* @__PURE__ */ new Set();
5073
- for (const entry of config.authority) {
5000
+ for (const entry of controlledPaths) {
5074
5001
  if (looksLikeGlob(entry)) {
5075
5002
  try {
5076
5003
  resolveRepoPath(rootDir, entry);
@@ -5078,17 +5005,17 @@ var checkAuthority = async (rootDir, config) => {
5078
5005
  diagnostics.push({
5079
5006
  category: "authority",
5080
5007
  severity: "error",
5081
- message: `Authority entry ${entry} must stay inside the repository root.`,
5008
+ message: `Truthmark-controlled path ${entry} must stay inside the repository root.`,
5082
5009
  file: entry
5083
5010
  });
5084
5011
  continue;
5085
5012
  }
5086
- const matches = (await fg3([entry], { cwd: rootDir, onlyFiles: true })).sort();
5013
+ const matches = (await fg2([entry], { cwd: rootDir, onlyFiles: true })).sort();
5087
5014
  if (matches.length === 0) {
5088
5015
  diagnostics.push({
5089
5016
  category: "authority",
5090
5017
  severity: "review",
5091
- message: `Authority glob ${entry} did not match any files.`,
5018
+ message: `Truthmark-controlled glob ${entry} did not match any files.`,
5092
5019
  file: entry
5093
5020
  });
5094
5021
  }
@@ -5100,7 +5027,7 @@ var checkAuthority = async (rootDir, config) => {
5100
5027
  diagnostics.push({
5101
5028
  category: "authority",
5102
5029
  severity: "error",
5103
- message: `Authority path ${match} must stay inside the repository root.`,
5030
+ message: `Truthmark-controlled path ${match} must stay inside the repository root.`,
5104
5031
  file: match
5105
5032
  });
5106
5033
  continue;
@@ -5120,7 +5047,7 @@ var checkAuthority = async (rootDir, config) => {
5120
5047
  diagnostics.push({
5121
5048
  category: "authority",
5122
5049
  severity: "error",
5123
- message: `Authority entry ${entry} must stay inside the repository root.`,
5050
+ message: `Truthmark-controlled path ${entry} must stay inside the repository root.`,
5124
5051
  file: entry
5125
5052
  });
5126
5053
  continue;
@@ -5129,7 +5056,7 @@ var checkAuthority = async (rootDir, config) => {
5129
5056
  diagnostics.push({
5130
5057
  category: "authority",
5131
5058
  severity: "error",
5132
- message: `Missing authority file ${entry}.`,
5059
+ message: `Missing Truthmark-controlled file ${entry}.`,
5133
5060
  file: entry
5134
5061
  });
5135
5062
  continue;
@@ -5144,6 +5071,9 @@ var checkAuthority = async (rootDir, config) => {
5144
5071
  diagnostics
5145
5072
  };
5146
5073
  };
5074
+ var checkAuthority = async (rootDir, config) => {
5075
+ return checkControlledPaths(rootDir, config.truthmark.controlledPaths);
5076
+ };
5147
5077
 
5148
5078
  // src/checks/frontmatter.ts
5149
5079
  import fs10 from "fs/promises";
@@ -5320,12 +5250,12 @@ var checkLinks = async (rootDir, markdownPaths) => {
5320
5250
 
5321
5251
  // src/checks/areas.ts
5322
5252
  import fs13 from "fs/promises";
5323
- import fg5 from "fast-glob";
5253
+ import fg4 from "fast-glob";
5324
5254
  import micromatch4 from "micromatch";
5325
5255
 
5326
5256
  // src/routing/area-resolver.ts
5327
5257
  import fs12 from "fs/promises";
5328
- import fg4 from "fast-glob";
5258
+ import fg3 from "fast-glob";
5329
5259
  import micromatch2 from "micromatch";
5330
5260
  var unique = (values) => {
5331
5261
  return [...new Set(values)];
@@ -5490,7 +5420,7 @@ var resolveAreaRouting = async (rootDir, config) => {
5490
5420
  );
5491
5421
  }
5492
5422
  }
5493
- const routeFilesUnderRoot = await fg4([`${config.areaFilesRoot}/**/*.md`], {
5423
+ const routeFilesUnderRoot = await fg3([`${config.areaFilesRoot}/**/*.md`], {
5494
5424
  cwd: rootDir,
5495
5425
  onlyFiles: true,
5496
5426
  followSymbolicLinks: false
@@ -5652,7 +5582,7 @@ var classifyPath = (filePath, ignorePatterns) => {
5652
5582
  if (normalizedPath.startsWith(".truthmark/")) {
5653
5583
  return "derived";
5654
5584
  }
5655
- if (normalizedPath.startsWith(".claude/") || normalizedPath.startsWith(".codex/") || normalizedPath.startsWith(".gemini/commands/") || normalizedPath.startsWith(".opencode/") || normalizedPath === ".github/copilot-instructions.md" || normalizedPath.startsWith(".github/agents/truth-") || normalizedPath.startsWith(".github/prompts/truthmark-") || normalizedPath === "AGENTS.md" || normalizedPath === "CLAUDE.md" || normalizedPath === "GEMINI.md" || normalizedPath.startsWith(".gemini/commands/truthmark/")) {
5585
+ if (normalizedPath.startsWith(".agents/skills/truthmark-") || normalizedPath.startsWith(".claude/") || normalizedPath.startsWith(".codex/") || normalizedPath.startsWith(".gemini/agents/truth-") || normalizedPath.startsWith(".gemini/commands/") || normalizedPath.startsWith(".gemini/skills/truthmark-") || normalizedPath.startsWith(".opencode/") || normalizedPath === ".github/copilot-instructions.md" || normalizedPath.startsWith(".github/agents/truth-") || normalizedPath.startsWith(".github/prompts/truthmark-") || normalizedPath.startsWith(".github/skills/truthmark-") || normalizedPath === "AGENTS.md" || normalizedPath === "CLAUDE.md" || normalizedPath === "GEMINI.md" || normalizedPath.startsWith(".gemini/commands/truthmark/")) {
5656
5586
  return "derived";
5657
5587
  }
5658
5588
  if (ignorePatterns.length > 0 && micromatch3.isMatch(normalizedPath, ignorePatterns)) {
@@ -5728,11 +5658,11 @@ var isBroadCodeSurface = (pattern) => {
5728
5658
  };
5729
5659
  var checkAreas = async (rootDir, config) => {
5730
5660
  const routing = await resolveAreaRouting(rootDir, {
5731
- rootIndex: config.docs.routing.rootIndex,
5732
- areaFilesRoot: config.docs.routing.areaFilesRoot,
5661
+ rootIndex: config.truthmark.paths.routesIndex,
5662
+ areaFilesRoot: config.truthmark.paths.routeAreasRoot,
5733
5663
  truthDocsRoot: resolveTruthDocsRoot(config)
5734
5664
  });
5735
- const discoveredCodeFiles = await fg5([...COVERAGE_SCAN_PATTERNS], {
5665
+ const discoveredCodeFiles = await fg4([...COVERAGE_SCAN_PATTERNS], {
5736
5666
  cwd: rootDir,
5737
5667
  onlyFiles: true,
5738
5668
  ignore: config.ignore,
@@ -5785,7 +5715,7 @@ var checkAreas = async (rootDir, config) => {
5785
5715
  const routedGlobEntry = area.truthDocumentEntries.find(
5786
5716
  (entry) => entry.path === truthDocument
5787
5717
  );
5788
- const matches = (await fg5([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
5718
+ const matches = (await fg4([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
5789
5719
  if (matches.length === 0) {
5790
5720
  diagnostics.push({
5791
5721
  category: "area-index",
@@ -5888,7 +5818,7 @@ var checkAreas = async (rootDir, config) => {
5888
5818
  entry.valid = false;
5889
5819
  continue;
5890
5820
  }
5891
- const matches = await fg5([codeSurfaceEntry], {
5821
+ const matches = await fg4([codeSurfaceEntry], {
5892
5822
  cwd: rootDir,
5893
5823
  onlyFiles: true,
5894
5824
  followSymbolicLinks: false
@@ -6039,11 +5969,7 @@ var kindSpecificHeadingMessages = (source, kind) => {
6039
5969
  return [];
6040
5970
  };
6041
5971
  var decisionTruthGlobs = (config) => {
6042
- return [
6043
- config.docs.roots.architecture,
6044
- resolveTruthDocsRoot(config),
6045
- config.docs.roots.api
6046
- ].filter((root) => Boolean(root)).map((root) => `${root}/**/*.md`);
5972
+ return [`${resolveTruthDocsRoot(config)}/**/*.md`];
6047
5973
  };
6048
5974
  var isDecisionTruthCandidate = (config, filePath) => {
6049
5975
  return !filePath.endsWith("/README.md") && micromatch5.isMatch(filePath, decisionTruthGlobs(config));
@@ -6175,7 +6101,7 @@ import path8 from "path";
6175
6101
  import fs16 from "fs/promises";
6176
6102
  import path6 from "path";
6177
6103
  import { execa as execa2 } from "execa";
6178
- import fg6 from "fast-glob";
6104
+ import fg5 from "fast-glob";
6179
6105
  import matter2 from "gray-matter";
6180
6106
  import micromatch6 from "micromatch";
6181
6107
  var languageByExtension = /* @__PURE__ */ new Map([
@@ -6257,7 +6183,7 @@ var isIgnoredPath = (filePath, ignore) => {
6257
6183
  return micromatch6.isMatch(filePath, [...defaultIgnore, ...ignore]);
6258
6184
  };
6259
6185
  var discoverRepoFiles = async (rootDir, ignore) => {
6260
- const discoveredFiles = await gitDiscoverableFiles(rootDir) ?? await fg6(["**/*"], {
6186
+ const discoveredFiles = await gitDiscoverableFiles(rootDir) ?? await fg5(["**/*"], {
6261
6187
  cwd: rootDir,
6262
6188
  onlyFiles: true,
6263
6189
  dot: true,
@@ -6318,7 +6244,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6318
6244
  // src/repo-index/package-metadata.ts
6319
6245
  import fs17 from "fs/promises";
6320
6246
  import path7 from "path";
6321
- import fg7 from "fast-glob";
6247
+ import fg6 from "fast-glob";
6322
6248
  var packageManagerFor = async (rootDir, packageDir) => {
6323
6249
  const lockfiles = [
6324
6250
  ["package-lock.json", "npm"],
@@ -6338,7 +6264,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
6338
6264
  return "npm";
6339
6265
  };
6340
6266
  var discoverPackageMetadata = async (rootDir) => {
6341
- const packageFiles = await fg7(["package.json", "*/package.json", "packages/*/package.json"], {
6267
+ const packageFiles = await fg6(["package.json", "*/package.json", "packages/*/package.json"], {
6342
6268
  cwd: rootDir,
6343
6269
  onlyFiles: true,
6344
6270
  ignore: ["node_modules/**", "dist/**", "build/**"],
@@ -6372,8 +6298,8 @@ var buildRouteMap = async (rootDir) => {
6372
6298
  };
6373
6299
  }
6374
6300
  const routing = await resolveAreaRouting(rootDir, {
6375
- rootIndex: loadResult.config.docs.routing.rootIndex,
6376
- areaFilesRoot: loadResult.config.docs.routing.areaFilesRoot,
6301
+ rootIndex: loadResult.config.truthmark.paths.routesIndex,
6302
+ areaFilesRoot: loadResult.config.truthmark.paths.routeAreasRoot,
6377
6303
  truthDocsRoot: resolveTruthDocsRoot(loadResult.config)
6378
6304
  });
6379
6305
  return {
@@ -6878,16 +6804,37 @@ var buildImpactSet = async (cwd, options) => {
6878
6804
  };
6879
6805
  };
6880
6806
 
6807
+ // src/freshness/check.ts
6808
+ var checkFreshness = async (rootDir, _config, _truthDocumentPaths, base) => {
6809
+ const impactSet = await buildImpactSet(rootDir, { base });
6810
+ const diagnostics = [];
6811
+ for (const diagnostic of impactSet.diagnostics) {
6812
+ if (diagnostic.category !== "impact") {
6813
+ continue;
6814
+ }
6815
+ diagnostics.push({
6816
+ ...diagnostic,
6817
+ category: "freshness",
6818
+ message: diagnostic.message.replace("not mapped to a Truthmark route", "not routed to truth ownership")
6819
+ });
6820
+ }
6821
+ return {
6822
+ diagnostics,
6823
+ impactSet
6824
+ };
6825
+ };
6826
+
6881
6827
  // src/evidence/validate.ts
6882
6828
  import fs21 from "fs/promises";
6883
- import fg8 from "fast-glob";
6829
+ import fg7 from "fast-glob";
6884
6830
 
6885
6831
  // src/evidence/parse.ts
6886
6832
  import fs20 from "fs/promises";
6887
6833
  import path11 from "path";
6888
6834
  import matter3 from "gray-matter";
6889
6835
  import { parse as parse3 } from "yaml";
6890
- var evidenceBlockPattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
6836
+ var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
6837
+ var topLevelEvidenceMarkerPattern = /^evidence\s*:/imu;
6891
6838
  var repoRootPrefixes = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
6892
6839
  var normalizeReferencePath = (truthDocPath, referencePath) => {
6893
6840
  const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
@@ -6926,8 +6873,12 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
6926
6873
  source: "frontmatter"
6927
6874
  });
6928
6875
  }
6929
- for (const match of parsed.content.matchAll(evidenceBlockPattern)) {
6930
- const block = parse3(match[1] ?? "");
6876
+ for (const match of parsed.content.matchAll(yamlFencePattern)) {
6877
+ const yamlBlock = match[1] ?? "";
6878
+ if (!topLevelEvidenceMarkerPattern.test(yamlBlock)) {
6879
+ continue;
6880
+ }
6881
+ const block = parse3(yamlBlock);
6931
6882
  const rawEvidence = block && typeof block === "object" && "evidence" in block ? block.evidence : null;
6932
6883
  if (!Array.isArray(rawEvidence)) {
6933
6884
  continue;
@@ -6952,7 +6903,7 @@ var pathExists5 = async (filePath) => {
6952
6903
  }
6953
6904
  };
6954
6905
  var diagnosticFor = (reference, message) => ({
6955
- category: "freshness",
6906
+ category: "source-traceability",
6956
6907
  severity: "error",
6957
6908
  message,
6958
6909
  file: reference.truthDocPath,
@@ -6961,12 +6912,21 @@ var diagnosticFor = (reference, message) => ({
6961
6912
  source: reference.source
6962
6913
  }
6963
6914
  });
6915
+ var parseDiagnosticFor = (truthDocPath, error) => ({
6916
+ category: "source-traceability",
6917
+ severity: "error",
6918
+ message: `Malformed evidence YAML block in ${truthDocPath}: ${error instanceof Error ? error.message : String(error)}`,
6919
+ file: truthDocPath,
6920
+ data: {
6921
+ source: "evidence-block"
6922
+ }
6923
+ });
6964
6924
  var isGlobReference = (referencePath) => /[*?[\]{}()]/u.test(referencePath);
6965
6925
  var validateGlob = async (rootDir, reference) => {
6966
6926
  if (reference.path.startsWith("../") || reference.path.startsWith("/")) {
6967
6927
  return diagnosticFor(reference, `Referenced file pattern ${reference.path} must stay inside the repository root.`);
6968
6928
  }
6969
- const matches = await fg8(reference.path, {
6929
+ const matches = await fg7(reference.path, {
6970
6930
  cwd: rootDir,
6971
6931
  dot: true,
6972
6932
  onlyFiles: true,
@@ -7044,7 +7004,13 @@ var validateReference = async (rootDir, reference) => {
7044
7004
  var validateEvidenceReferences = async (rootDir, truthDocPaths) => {
7045
7005
  const diagnostics = [];
7046
7006
  for (const truthDocPath of [...truthDocPaths].sort()) {
7047
- const references = await parseEvidenceReferences(rootDir, truthDocPath);
7007
+ let references;
7008
+ try {
7009
+ references = await parseEvidenceReferences(rootDir, truthDocPath);
7010
+ } catch (error) {
7011
+ diagnostics.push(parseDiagnosticFor(truthDocPath, error));
7012
+ continue;
7013
+ }
7048
7014
  for (const reference of references) {
7049
7015
  diagnostics.push(...await validateReference(rootDir, reference));
7050
7016
  }
@@ -7052,25 +7018,135 @@ var validateEvidenceReferences = async (rootDir, truthDocPaths) => {
7052
7018
  return diagnostics;
7053
7019
  };
7054
7020
 
7055
- // src/freshness/check.ts
7056
- var checkFreshness = async (rootDir, _config, truthDocumentPaths, base) => {
7057
- const impactSet = await buildImpactSet(rootDir, { base });
7058
- const diagnostics = [...await validateEvidenceReferences(rootDir, truthDocumentPaths)];
7059
- for (const diagnostic of impactSet.diagnostics) {
7060
- if (diagnostic.category !== "impact") {
7061
- continue;
7021
+ // src/checks/scorecard.ts
7022
+ var TRUTH_HEALTH_SCORECARD_SCHEMA_VERSION = "truthmark-scorecard/v0";
7023
+ var TRUTH_HEALTH_DIMENSION_IDS = [
7024
+ "routing-coverage",
7025
+ "ownership-clarity",
7026
+ "source-traceability",
7027
+ "branch-freshness",
7028
+ "generated-surface-freshness",
7029
+ "truth-doc-structure",
7030
+ "decision-rationale-preservation"
7031
+ ];
7032
+ var EVIDENCE_LIMIT = 2;
7033
+ var textIncludesAny = (text, needles) => {
7034
+ const normalized = text.toLowerCase();
7035
+ return needles.some((needle) => normalized.includes(needle));
7036
+ };
7037
+ var diagnosticText = (diagnostic) => {
7038
+ const dataText = diagnostic.data ? JSON.stringify(diagnostic.data) : "";
7039
+ return `${diagnostic.message} ${diagnostic.file ?? ""} ${diagnostic.area ?? ""} ${dataText}`;
7040
+ };
7041
+ var isRouteAmbiguityDiagnostic = (diagnostic) => diagnostic.category === "context-pack" && textIncludesAny(diagnosticText(diagnostic), [
7042
+ "route",
7043
+ "routing",
7044
+ "ownership",
7045
+ "write boundary",
7046
+ "allowed write"
7047
+ ]);
7048
+ var isSourceTraceabilityDiagnostic = (diagnostic) => diagnostic.category === "source-traceability" || diagnostic.category === "links" && textIncludesAny(diagnosticText(diagnostic), [
7049
+ "source_of_truth",
7050
+ "source of truth",
7051
+ "source evidence",
7052
+ "evidence"
7053
+ ]);
7054
+ var isMarkdownShapeDiagnostic = (diagnostic) => diagnostic.category === "authority" && textIncludesAny(diagnosticText(diagnostic), ["markdown", "heading", "section"]);
7055
+ var isOwnershipFreshnessDiagnostic = (diagnostic) => diagnostic.category === "freshness" && textIncludesAny(diagnosticText(diagnostic), [
7056
+ "route",
7057
+ "routing",
7058
+ "ownership",
7059
+ "truth owner",
7060
+ "truth ownership",
7061
+ "affected truth document"
7062
+ ]);
7063
+ var isDecisionRationaleDiagnostic = (diagnostic) => diagnostic.category === "doc-structure" && textIncludesAny(diagnosticText(diagnostic), ["product decisions", "rationale"]);
7064
+ var mapsToDimension = (diagnostic, dimensionId) => {
7065
+ switch (dimensionId) {
7066
+ case "routing-coverage":
7067
+ return ["config", "authority", "area-index", "coverage", "repo-index"].includes(
7068
+ diagnostic.category
7069
+ );
7070
+ case "ownership-clarity":
7071
+ return ["config", "area-index", "coverage", "impact"].includes(diagnostic.category) || isRouteAmbiguityDiagnostic(diagnostic) || isOwnershipFreshnessDiagnostic(diagnostic);
7072
+ case "source-traceability":
7073
+ return isSourceTraceabilityDiagnostic(diagnostic);
7074
+ case "branch-freshness":
7075
+ return diagnostic.category === "freshness";
7076
+ case "generated-surface-freshness":
7077
+ return diagnostic.category === "config" || diagnostic.category === "generated-surface";
7078
+ case "truth-doc-structure":
7079
+ return diagnostic.category === "config" || diagnostic.category === "frontmatter" || diagnostic.category === "links" || diagnostic.category === "doc-structure" || isMarkdownShapeDiagnostic(diagnostic);
7080
+ case "decision-rationale-preservation":
7081
+ return isDecisionRationaleDiagnostic(diagnostic);
7082
+ }
7083
+ };
7084
+ var checkerRanForDimension = (dimensionId, context) => {
7085
+ switch (dimensionId) {
7086
+ case "routing-coverage":
7087
+ return context.routingChecksRan ?? true;
7088
+ case "ownership-clarity":
7089
+ return context.ownershipChecksRan ?? true;
7090
+ case "source-traceability":
7091
+ return context.evidenceChecksRan ?? true;
7092
+ case "branch-freshness":
7093
+ return context.branchFreshnessRan;
7094
+ case "generated-surface-freshness":
7095
+ return context.generatedSurfaceChecksRan ?? true;
7096
+ case "truth-doc-structure":
7097
+ return context.truthDocStructureChecksRan ?? true;
7098
+ case "decision-rationale-preservation":
7099
+ return context.decisionRationaleChecksRan ?? true;
7100
+ }
7101
+ };
7102
+ var statusForDiagnostics = (diagnostics, diagnosticIndexes, checkerRan) => {
7103
+ if (diagnosticIndexes.length > 0) {
7104
+ return diagnosticIndexes.some((index) => diagnostics[index]?.severity === "error") ? "fail" : "warn";
7105
+ }
7106
+ return checkerRan ? "pass" : "not-run";
7107
+ };
7108
+ var evidenceForDiagnostics = (diagnostics, diagnosticIndexes, status, dimensionId, context) => {
7109
+ if (status === "pass") {
7110
+ return void 0;
7111
+ }
7112
+ if (diagnosticIndexes.length === 0) {
7113
+ if (dimensionId === "branch-freshness") {
7114
+ return [context.branchFreshnessNotRunReason ?? "base not supplied"];
7062
7115
  }
7063
- diagnostics.push({
7064
- ...diagnostic,
7065
- category: "freshness",
7066
- message: diagnostic.message.replace("not mapped to a Truthmark route", "not routed to truth ownership")
7067
- });
7116
+ return ["checker not run"];
7068
7117
  }
7069
- return {
7070
- diagnostics,
7071
- impactSet
7072
- };
7118
+ return diagnosticIndexes.slice(0, EVIDENCE_LIMIT).map((index) => {
7119
+ const diagnostic = diagnostics[index];
7120
+ const subject = diagnostic.file ?? diagnostic.area;
7121
+ return subject ? `${diagnostic.category}:${subject}` : diagnostic.category;
7122
+ });
7073
7123
  };
7124
+ var buildTruthHealthScorecard = (diagnostics, context) => ({
7125
+ schemaVersion: TRUTH_HEALTH_SCORECARD_SCHEMA_VERSION,
7126
+ dimensions: TRUTH_HEALTH_DIMENSION_IDS.map((dimensionId) => {
7127
+ const diagnosticIndexes = diagnostics.flatMap(
7128
+ (diagnostic, index) => mapsToDimension(diagnostic, dimensionId) ? [index] : []
7129
+ );
7130
+ const status = statusForDiagnostics(
7131
+ diagnostics,
7132
+ diagnosticIndexes,
7133
+ checkerRanForDimension(dimensionId, context)
7134
+ );
7135
+ const evidence = evidenceForDiagnostics(
7136
+ diagnostics,
7137
+ diagnosticIndexes,
7138
+ status,
7139
+ dimensionId,
7140
+ context
7141
+ );
7142
+ return {
7143
+ id: dimensionId,
7144
+ status,
7145
+ diagnosticIndexes,
7146
+ ...evidence ? { evidence } : {}
7147
+ };
7148
+ })
7149
+ });
7074
7150
 
7075
7151
  // src/checks/check.ts
7076
7152
  var summarizeDiagnostics = (diagnostics) => {
@@ -7087,12 +7163,23 @@ var runCheck = async (cwd, options = {}) => {
7087
7163
  const branchScope = await getBranchScopeData(rootDir);
7088
7164
  const loadResult = await loadConfig(rootDir);
7089
7165
  if (!loadResult.config) {
7166
+ const scorecard2 = buildTruthHealthScorecard(loadResult.diagnostics, {
7167
+ branchFreshnessRan: false,
7168
+ branchFreshnessNotRunReason: options.base ? "config unavailable" : "base not supplied",
7169
+ routingChecksRan: false,
7170
+ ownershipChecksRan: false,
7171
+ evidenceChecksRan: false,
7172
+ generatedSurfaceChecksRan: false,
7173
+ truthDocStructureChecksRan: false,
7174
+ decisionRationaleChecksRan: false
7175
+ });
7090
7176
  return {
7091
7177
  command: "check",
7092
7178
  summary: summarizeDiagnostics(loadResult.diagnostics),
7093
7179
  diagnostics: loadResult.diagnostics,
7094
7180
  data: {
7095
- branchScope
7181
+ branchScope,
7182
+ scorecard: scorecard2
7096
7183
  }
7097
7184
  };
7098
7185
  }
@@ -7113,6 +7200,7 @@ var runCheck = async (cwd, options = {}) => {
7113
7200
  areas.truthDocumentEntries
7114
7201
  );
7115
7202
  const generatedSurfaces = await checkGeneratedSurfaces(rootDir, loadResult.config);
7203
+ const sourceTraceability = await validateEvidenceReferences(rootDir, areas.truthDocumentPaths);
7116
7204
  const freshness = options.base ? await checkFreshness(rootDir, loadResult.config, areas.truthDocumentPaths, options.base) : null;
7117
7205
  const diagnostics = [
7118
7206
  ...loadResult.diagnostics,
@@ -7122,6 +7210,7 @@ var runCheck = async (cwd, options = {}) => {
7122
7210
  ...areas.diagnostics,
7123
7211
  ...decisionSections,
7124
7212
  ...generatedSurfaces,
7213
+ ...sourceTraceability,
7125
7214
  ...freshness?.diagnostics ?? []
7126
7215
  ];
7127
7216
  const truthVisibility = {
@@ -7136,6 +7225,11 @@ var runCheck = async (cwd, options = {}) => {
7136
7225
  topologyPressureCount: areas.topologyPressureCount,
7137
7226
  freshnessDiagnosticCount: freshness?.diagnostics.length ?? 0
7138
7227
  };
7228
+ const scorecard = buildTruthHealthScorecard(diagnostics, {
7229
+ branchFreshnessRan: Boolean(freshness),
7230
+ branchFreshnessNotRunReason: options.base ? "freshness checker skipped" : "base not supplied",
7231
+ evidenceChecksRan: true
7232
+ });
7139
7233
  return {
7140
7234
  command: "check",
7141
7235
  summary: summarizeDiagnostics(diagnostics),
@@ -7143,6 +7237,7 @@ var runCheck = async (cwd, options = {}) => {
7143
7237
  data: {
7144
7238
  branchScope,
7145
7239
  truthVisibility,
7240
+ scorecard,
7146
7241
  ...freshness ? { impactSet: freshness.impactSet } : {}
7147
7242
  }
7148
7243
  };
@@ -7151,7 +7246,7 @@ var runCheck = async (cwd, options = {}) => {
7151
7246
  // src/context-pack/build.ts
7152
7247
  import fs22 from "fs/promises";
7153
7248
  import path12 from "path";
7154
- import fg9 from "fast-glob";
7249
+ import fg8 from "fast-glob";
7155
7250
  var uniqueSorted2 = (values) => [...new Set(values)].sort();
7156
7251
  var repoRootPrefixes2 = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
7157
7252
  var isGlobReference2 = (referencePath) => /[*?[\]{}()]/u.test(referencePath);
@@ -7171,9 +7266,19 @@ var readIfExists = async (rootDir, filePath) => {
7171
7266
  return null;
7172
7267
  }
7173
7268
  };
7174
- var boundedContent = (filePath, content, warnings) => {
7269
+ var boundContent = (content) => {
7175
7270
  const lines = content.split("\n");
7176
7271
  if (lines.length <= 200) {
7272
+ return { content, truncated: false };
7273
+ }
7274
+ return {
7275
+ content: [...lines.slice(0, 80), "...", ...lines.slice(-40)].join("\n"),
7276
+ truncated: true
7277
+ };
7278
+ };
7279
+ var boundedContent = (filePath, content, warnings) => {
7280
+ const bounded = boundContent(content);
7281
+ if (!bounded.truncated) {
7177
7282
  return { path: filePath, content, truncated: false };
7178
7283
  }
7179
7284
  warnings.push({
@@ -7184,16 +7289,25 @@ var boundedContent = (filePath, content, warnings) => {
7184
7289
  });
7185
7290
  return {
7186
7291
  path: filePath,
7187
- content: [...lines.slice(0, 80), "...", ...lines.slice(-40)].join("\n"),
7292
+ content: bounded.content,
7188
7293
  truncated: true
7189
7294
  };
7190
7295
  };
7191
- var documentsFor = async (rootDir, paths) => {
7296
+ var documentsFor = async (rootDir, paths, warnings) => {
7192
7297
  const documents = [];
7193
7298
  for (const filePath of uniqueSorted2(paths)) {
7194
7299
  const content = await readIfExists(rootDir, filePath);
7195
7300
  if (content !== null) {
7196
- documents.push({ path: filePath, content });
7301
+ const bounded = boundContent(content);
7302
+ if (bounded.truncated) {
7303
+ warnings.push({
7304
+ category: "context-pack",
7305
+ severity: "review",
7306
+ message: `Context truth doc ${filePath} was truncated to fit ContextPack v0 bounds.`,
7307
+ file: filePath
7308
+ });
7309
+ }
7310
+ documents.push({ path: filePath, content: bounded.content, truncated: bounded.truncated });
7197
7311
  }
7198
7312
  }
7199
7313
  return documents;
@@ -7222,7 +7336,7 @@ var sourceOfTruthPathsFor = async (rootDir, docs, truthDocPaths) => {
7222
7336
  }
7223
7337
  if (isGlobReference2(normalizedPath)) {
7224
7338
  sourcePaths.push(
7225
- ...await fg9(normalizedPath, {
7339
+ ...await fg8(normalizedPath, {
7226
7340
  cwd: rootDir,
7227
7341
  dot: true,
7228
7342
  onlyFiles: true,
@@ -7236,12 +7350,12 @@ var sourceOfTruthPathsFor = async (rootDir, docs, truthDocPaths) => {
7236
7350
  }
7237
7351
  return uniqueSorted2(sourcePaths);
7238
7352
  };
7239
- var writePathsFor = (workflow, truthDocs, routes) => {
7353
+ var writePathsFor = (workflow, routeIndexPath, truthDocs, routes) => {
7240
7354
  if (workflow === "truth-sync") {
7241
- return uniqueSorted2(["docs/truthmark/areas.md", ...truthDocs]);
7355
+ return uniqueSorted2([routeIndexPath, ...truthDocs]);
7242
7356
  }
7243
7357
  if (workflow === "truth-document") {
7244
- return uniqueSorted2(["docs/truthmark/areas.md", ...truthDocs]);
7358
+ return uniqueSorted2([routeIndexPath, ...truthDocs]);
7245
7359
  }
7246
7360
  return uniqueSorted2(routes.flatMap((route) => route.codeSurface));
7247
7361
  };
@@ -7251,9 +7365,11 @@ var testCommandsFor = (affectedTests) => {
7251
7365
  var buildContextPack = async (cwd, options) => {
7252
7366
  const repoIndex = await buildRepoIndex(cwd);
7253
7367
  const rootDir = repoIndex.repository.root;
7368
+ const loadResult = await loadConfig(rootDir);
7369
+ const config = loadResult.config ?? (loadResult.status === "missing" ? createDefaultConfig() : null);
7254
7370
  const impactSet = options.base ? await buildImpactSet(rootDir, { base: options.base }) : null;
7255
7371
  const routeMap = impactSet ? repoIndex.routeMap : repoIndex.routeMap;
7256
- const warnings = [];
7372
+ const warnings = loadResult.status === "invalid" ? [...loadResult.diagnostics] : [];
7257
7373
  const truthDocPaths = impactSet?.affectedTruthDocs ?? (options.workflow === "truth-realize" ? [] : routeMap.routes.flatMap((route) => route.truthDocs));
7258
7374
  const contextRoutes = impactSet?.affectedRoutes ?? (options.workflow === "truth-realize" ? [] : routeMap.routes);
7259
7375
  if (options.workflow === "truth-realize" && !impactSet) {
@@ -7274,8 +7390,13 @@ var buildContextPack = async (cwd, options) => {
7274
7390
  base: options.base ?? null,
7275
7391
  impactSet,
7276
7392
  routeMap,
7277
- allowedWritePaths: writePathsFor(options.workflow, truthDocPaths, contextRoutes),
7278
- truthDocs: await documentsFor(rootDir, truthDocPaths),
7393
+ allowedWritePaths: config === null ? [] : writePathsFor(
7394
+ options.workflow,
7395
+ config.truthmark.paths.routesIndex,
7396
+ truthDocPaths,
7397
+ contextRoutes
7398
+ ),
7399
+ truthDocs: await documentsFor(rootDir, truthDocPaths, warnings),
7279
7400
  sourceFiles: await sourceFilesFor(rootDir, sourceFilePaths, warnings),
7280
7401
  testCommands: testCommandsFor(impactSet?.affectedTests ?? []),
7281
7402
  warnings
@@ -7294,7 +7415,7 @@ var renderContextPackMarkdown = (pack) => {
7294
7415
  ...pack.allowedWritePaths.map((filePath) => `- ${filePath}`),
7295
7416
  "",
7296
7417
  "## Truth Docs",
7297
- ...pack.truthDocs.map((doc) => `- ${doc.path}`),
7418
+ ...pack.truthDocs.map((doc) => `- ${doc.path}${doc.truncated ? " (truncated)" : ""}`),
7298
7419
  "",
7299
7420
  "## Source Files",
7300
7421
  ...pack.sourceFiles.map((file) => `- ${file.path}${file.truncated ? " (truncated)" : ""}`),
@@ -7306,6 +7427,198 @@ var renderContextPackMarkdown = (pack) => {
7306
7427
  `;
7307
7428
  };
7308
7429
 
7430
+ // src/workflow-state/build.ts
7431
+ import { execa as execa5 } from "execa";
7432
+
7433
+ // src/workflow-state/action-context.ts
7434
+ var uniqueSorted3 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
7435
+ var helperCommandsFor = (manifestEntry) => (manifestEntry.helpers ?? []).map((helper) => ({
7436
+ id: helper.id,
7437
+ runner: helper.runner,
7438
+ argv: [...helper.command.argv],
7439
+ optional: helper.optional
7440
+ }));
7441
+ var evidenceFor = (manifestEntry) => manifestEntry.requiredGates.filter((gate) => /evidence|ownership|containment/iu.test(gate));
7442
+ var baseContext = (manifestEntry, mode, allowedWritePaths, forbiddenWritePaths, writeLeaseRequired) => ({
7443
+ mode,
7444
+ allowedWritePaths: uniqueSorted3(allowedWritePaths),
7445
+ forbiddenWritePaths: uniqueSorted3(forbiddenWritePaths),
7446
+ stopConditions: [...manifestEntry.negativeTriggers, ...manifestEntry.forbiddenAdjacency],
7447
+ requiredEvidence: evidenceFor(manifestEntry),
7448
+ helperValidationCommands: helperCommandsFor(manifestEntry),
7449
+ writeLeaseRequired
7450
+ });
7451
+ var buildWorkflowActionContext = (manifestEntry, data = {}) => {
7452
+ if (manifestEntry.id === "truthmark-preview" || manifestEntry.id === "truthmark-check") {
7453
+ return baseContext(manifestEntry, "read-only", [], [], false);
7454
+ }
7455
+ if (manifestEntry.id === "truthmark-sync" || manifestEntry.id === "truthmark-document") {
7456
+ return baseContext(
7457
+ manifestEntry,
7458
+ "truth-doc-write",
7459
+ [...data.routeIndexPath ? [data.routeIndexPath] : [], ...data.routeFiles ?? [], ...data.truthDocs ?? []],
7460
+ [],
7461
+ true
7462
+ );
7463
+ }
7464
+ if (manifestEntry.id === "truthmark-structure") {
7465
+ return baseContext(
7466
+ manifestEntry,
7467
+ "route-write",
7468
+ [
7469
+ ...data.routeIndexPath ? [data.routeIndexPath] : [],
7470
+ ...data.routeFiles ?? [],
7471
+ ...data.starterTruthDocs ?? []
7472
+ ],
7473
+ [],
7474
+ true
7475
+ );
7476
+ }
7477
+ if (manifestEntry.id === "truthmark-realize") {
7478
+ return baseContext(
7479
+ manifestEntry,
7480
+ "code-write",
7481
+ data.codeWritePaths ?? [],
7482
+ [
7483
+ ...data.routeIndexPath ? [data.routeIndexPath] : [],
7484
+ ...data.routeFiles ?? [],
7485
+ ...data.truthRoot ? [`${data.truthRoot}/**/*.md`] : [],
7486
+ ...data.truthDocs ?? []
7487
+ ],
7488
+ false
7489
+ );
7490
+ }
7491
+ return baseContext(
7492
+ manifestEntry,
7493
+ "portal-write",
7494
+ data.portalEnabled && data.portalOutputPath ? [`${data.portalOutputPath}/**`] : [],
7495
+ [],
7496
+ false
7497
+ );
7498
+ };
7499
+
7500
+ // src/workflow-state/build.ts
7501
+ var helperCommandsFor2 = (workflow) => (TRUTHMARK_WORKFLOW_MANIFEST[workflow].helpers ?? []).map((helper) => ({
7502
+ id: helper.id,
7503
+ runner: helper.runner,
7504
+ argv: [...helper.command.argv],
7505
+ optional: helper.optional
7506
+ }));
7507
+ var uniqueSorted4 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
7508
+ var isWriteCapable = (workflow) => !["truthmark-preview", "truthmark-check"].includes(workflow);
7509
+ var DEFAULT_BASE_CANDIDATES = ["@{upstream}", "origin/main", "main", "origin/master", "master"];
7510
+ var selectComparisonBase = async (rootDir, suppliedBase) => {
7511
+ if (suppliedBase) {
7512
+ return suppliedBase;
7513
+ }
7514
+ for (const candidate of DEFAULT_BASE_CANDIDATES) {
7515
+ const result = await execa5("git", ["rev-parse", "--verify", `${candidate}^{commit}`], {
7516
+ cwd: rootDir,
7517
+ reject: false
7518
+ });
7519
+ if ((result.exitCode ?? 1) === 0) {
7520
+ return candidate;
7521
+ }
7522
+ }
7523
+ return null;
7524
+ };
7525
+ var routeFilesFor = (repoIndex) => uniqueSorted4(repoIndex.routeMap.routes.map((route) => route.sourcePath));
7526
+ var hasUnmappedFunctionalChange = (impactSet) => impactSet?.diagnostics.some(
7527
+ (diagnostic) => diagnostic.category === "impact" && /not mapped to a Truthmark route|no affected truth document/u.test(diagnostic.message)
7528
+ ) ?? false;
7529
+ var applicabilityFor = (workflow, diagnostics, impactSet) => {
7530
+ const reasons = [];
7531
+ if (diagnostics.some((diagnostic) => diagnostic.message.includes("Missing .truthmark/config.yml"))) {
7532
+ reasons.push("Missing .truthmark/config.yml.");
7533
+ return { state: isWriteCapable(workflow) ? "blocked" : "not_applicable", reasons };
7534
+ }
7535
+ if (workflow === "truthmark-sync" && !impactSet) {
7536
+ reasons.push("truthmark-sync requires --base to derive bounded truth-doc write paths.");
7537
+ return { state: "blocked", reasons };
7538
+ }
7539
+ if (workflow === "truthmark-realize" && !impactSet) {
7540
+ reasons.push("truthmark-realize requires --base to derive bounded allowed write paths.");
7541
+ return { state: "blocked", reasons };
7542
+ }
7543
+ if (hasUnmappedFunctionalChange(impactSet)) {
7544
+ reasons.push("Changed functional files have ambiguous or missing Truthmark route ownership.");
7545
+ return { state: "ambiguous", reasons };
7546
+ }
7547
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
7548
+ reasons.push("Existing diagnostics contain errors that block safe workflow execution.");
7549
+ return { state: "blocked", reasons };
7550
+ }
7551
+ return { state: "applicable", reasons };
7552
+ };
7553
+ var contextDataFor = (workflow, repoIndex, config, impactSet) => {
7554
+ if (!config) {
7555
+ return {};
7556
+ }
7557
+ const routeFiles = routeFilesFor(repoIndex);
7558
+ const truthDocs = uniqueSorted4(
7559
+ impactSet?.affectedTruthDocs ?? repoIndex.routeMap.routes.flatMap((route) => route.truthDocs)
7560
+ );
7561
+ return {
7562
+ routeIndexPath: config.truthmark.paths.routesIndex,
7563
+ routeFiles,
7564
+ truthRoot: config.truthmark.paths.truthRoot,
7565
+ truthDocs,
7566
+ starterTruthDocs: workflow === "truthmark-structure" ? truthDocs : [],
7567
+ codeWritePaths: workflow === "truthmark-realize" ? uniqueSorted4(impactSet?.affectedRoutes.flatMap((route) => route.codeSurface) ?? []) : [],
7568
+ portalEnabled: config.truthmark.generated.portal.enabled,
7569
+ portalOutputPath: config.truthmark.paths.portalOutput,
7570
+ routes: repoIndex.routeMap.routes
7571
+ };
7572
+ };
7573
+ var nextStepsFor = (workflow, applicability, comparisonBase) => {
7574
+ if (applicability.state === "ambiguous") {
7575
+ return ["Run Truth Structure or repair route ownership before writing truth docs."];
7576
+ }
7577
+ if ((workflow === "truthmark-sync" || workflow === "truthmark-realize") && applicability.state === "blocked" && !comparisonBase) {
7578
+ return [
7579
+ workflow === "truthmark-sync" ? "Rerun with --base <ref> so Truthmark can derive bounded truth-doc write paths." : "Rerun with --base <ref> so Truthmark can derive bounded allowed code-write paths."
7580
+ ];
7581
+ }
7582
+ return [];
7583
+ };
7584
+ var buildWorkflowState = async (cwd, options) => {
7585
+ const manifestEntry = TRUTHMARK_WORKFLOW_MANIFEST[options.workflow];
7586
+ if (!manifestEntry) {
7587
+ throw new Error(`Unknown Truthmark workflow: ${String(options.workflow)}`);
7588
+ }
7589
+ const repoIndex = await buildRepoIndex(cwd);
7590
+ const rootDir = repoIndex.repository.root;
7591
+ const loadResult = await loadConfig(rootDir);
7592
+ const comparisonBase = options.base ? options.base : options.workflow === "truthmark-sync" ? await selectComparisonBase(rootDir) : null;
7593
+ const impactSet = comparisonBase ? await buildImpactSet(rootDir, { base: comparisonBase }) : null;
7594
+ const checkResult = await runCheck(cwd, comparisonBase ? { base: comparisonBase } : {});
7595
+ const diagnostics = [
7596
+ ...loadResult.diagnostics,
7597
+ ...repoIndex.diagnostics,
7598
+ ...impactSet?.diagnostics ?? [],
7599
+ ...checkResult.diagnostics
7600
+ ];
7601
+ const applicability = applicabilityFor(options.workflow, diagnostics, impactSet);
7602
+ const actionData = applicability.state === "blocked" || applicability.state === "ambiguous" ? {} : contextDataFor(options.workflow, repoIndex, loadResult.config, impactSet);
7603
+ return {
7604
+ schemaVersion: "truthmark-workflow/v0",
7605
+ workflow: options.workflow,
7606
+ applicability,
7607
+ actionContext: buildWorkflowActionContext(manifestEntry, actionData),
7608
+ changedFiles: impactSet?.changedFiles ?? [],
7609
+ affectedRoutes: impactSet?.affectedRoutes ?? [],
7610
+ targetTruthDocs: applicability.state === "ambiguous" ? [] : impactSet?.affectedTruthDocs ?? [],
7611
+ diagnostics,
7612
+ checks: {
7613
+ required: [...manifestEntry.requiredGates],
7614
+ recommended: [...manifestEntry.positiveTriggers],
7615
+ helpers: helperCommandsFor2(options.workflow)
7616
+ },
7617
+ nextSteps: nextStepsFor(options.workflow, applicability, comparisonBase),
7618
+ reportSections: [...manifestEntry.reportSections]
7619
+ };
7620
+ };
7621
+
7309
7622
  // src/cli/handlers.ts
7310
7623
  import fs23 from "fs/promises";
7311
7624
 
@@ -7672,9 +7985,26 @@ var runImpact = async (options) => {
7672
7985
  var isContextPackWorkflow = (value) => {
7673
7986
  return value === "truth-sync" || value === "truth-document" || value === "truth-realize";
7674
7987
  };
7675
- var isContextPackFormat = (value) => {
7676
- return value === void 0 || value === "json" || value === "markdown";
7988
+ var isContextMarkdownFormat = (value) => {
7989
+ return value === void 0 || value === "markdown";
7990
+ };
7991
+ var isTruthmarkWorkflowId = (value) => {
7992
+ return typeof value === "string" && TRUTHMARK_WORKFLOW_IDS.includes(value);
7677
7993
  };
7994
+ var invalidWorkflowResult = (command, workflow) => ({
7995
+ command,
7996
+ summary: workflow ? `Truthmark workflow requires a supported full workflow ID; received ${workflow}.` : "Truthmark workflow requires --workflow.",
7997
+ diagnostics: [
7998
+ {
7999
+ category: "workflow-state",
8000
+ severity: "error",
8001
+ message: workflow ? `Unknown Truthmark workflow: ${workflow}. Use a canonical full manifest ID such as truthmark-sync or truthmark-check.` : `truthmark ${command} requires --workflow <workflow>.`
8002
+ }
8003
+ ],
8004
+ data: {
8005
+ request: workflow ? { workflow } : {}
8006
+ }
8007
+ });
7678
8008
  var readHelperFile = async (filePath, helper) => {
7679
8009
  try {
7680
8010
  return await fs23.readFile(filePath, "utf8");
@@ -7716,7 +8046,20 @@ var runContext = async (options) => {
7716
8046
  ]
7717
8047
  };
7718
8048
  }
7719
- if (!isContextPackFormat(options.format)) {
8049
+ if (options.format === "json") {
8050
+ return {
8051
+ command: "context",
8052
+ summary: "Truthmark context no longer supports JSON ContextPack output.",
8053
+ diagnostics: [
8054
+ {
8055
+ category: "context-pack",
8056
+ severity: "error",
8057
+ message: "JSON ContextPack output was removed in v2; use --format markdown."
8058
+ }
8059
+ ]
8060
+ };
8061
+ }
8062
+ if (!isContextMarkdownFormat(options.format)) {
7720
8063
  return {
7721
8064
  command: "context",
7722
8065
  summary: "Truthmark context requires a supported --format value.",
@@ -7724,7 +8067,7 @@ var runContext = async (options) => {
7724
8067
  {
7725
8068
  category: "context-pack",
7726
8069
  severity: "error",
7727
- message: "truthmark context requires --format json or markdown."
8070
+ message: "truthmark context supports only --format markdown; JSON ContextPack output was removed in v2."
7728
8071
  }
7729
8072
  ]
7730
8073
  };
@@ -7734,13 +8077,36 @@ var runContext = async (options) => {
7734
8077
  base: options.base
7735
8078
  });
7736
8079
  const diagnostics = contextPack.warnings;
8080
+ const summary = `Truthmark context generated ${contextPack.workflow} ContextPack with ${diagnostics.length} warnings.`;
8081
+ const markdown = renderContextPackMarkdown(contextPack);
7737
8082
  return {
7738
8083
  command: "context",
7739
- summary: `Truthmark context generated ${contextPack.workflow} ContextPack with ${diagnostics.length} warnings.`,
8084
+ summary,
7740
8085
  diagnostics,
7741
8086
  data: {
7742
- contextPack,
7743
- ...options.format === "markdown" ? { markdown: renderContextPackMarkdown(contextPack) } : {}
8087
+ markdown,
8088
+ summary
8089
+ }
8090
+ };
8091
+ };
8092
+ var runWorkflowStatus = async (options) => {
8093
+ if (!isTruthmarkWorkflowId(options.workflow)) {
8094
+ return invalidWorkflowResult("workflow status", options.workflow);
8095
+ }
8096
+ const workflowState = await buildWorkflowState(process.cwd(), {
8097
+ workflow: options.workflow,
8098
+ ...options.base ? { base: options.base } : {}
8099
+ });
8100
+ return {
8101
+ command: "workflow status",
8102
+ summary: `Truthmark workflow status completed for ${options.workflow}.`,
8103
+ diagnostics: workflowState.diagnostics,
8104
+ data: {
8105
+ request: {
8106
+ workflow: options.workflow,
8107
+ ...options.base ? { base: options.base } : {}
8108
+ },
8109
+ workflowState
7744
8110
  }
7745
8111
  };
7746
8112
  };
@@ -7758,7 +8124,7 @@ var writeResult = (result, options) => {
7758
8124
  markFailedWhenErrorDiagnosticsExist(result);
7759
8125
  };
7760
8126
  var writeContextResult = (result, options) => {
7761
- if (!options.json && options.format === "markdown" && typeof result.data?.markdown === "string") {
8127
+ if (!options.json && typeof result.data?.markdown === "string") {
7762
8128
  process.stdout.write(result.data.markdown);
7763
8129
  markFailedWhenErrorDiagnosticsExist(result);
7764
8130
  return;
@@ -7819,7 +8185,7 @@ var buildProgram = () => {
7819
8185
  writeResult(await runImpact({ base: options.base }), options);
7820
8186
  });
7821
8187
  addJsonOption(
7822
- program.command("context").description("Generate a bounded workflow context pack.").requiredOption("--workflow <workflow>", "Workflow name: truth-sync, truth-document, or truth-realize").option("--base <ref>", "Base Git ref for impact-backed packs").option("--format <format>", "Output format: json or markdown", "json")
8188
+ program.command("context").description("Generate a bounded workflow context pack.").requiredOption("--workflow <workflow>", "Workflow name: truth-sync, truth-document, or truth-realize").option("--base <ref>", "Base Git ref for impact-backed packs").option("--format <format>", "Output format: markdown", "markdown")
7823
8189
  ).action(async (options) => {
7824
8190
  writeContextResult(
7825
8191
  await runContext({
@@ -7830,6 +8196,18 @@ var buildProgram = () => {
7830
8196
  options
7831
8197
  );
7832
8198
  });
8199
+ const workflow = program.command("workflow").description("Inspect agent-facing Truthmark workflow state.");
8200
+ addJsonOption(
8201
+ workflow.command("status").description("Return schema-versioned workflow state for a canonical workflow ID.").option("--workflow <workflow>", "Canonical workflow ID, such as truthmark-sync").option("--base <ref>", "Base Git ref for impact-backed workflow state")
8202
+ ).action(async (options) => {
8203
+ writeResult(
8204
+ await runWorkflowStatus({
8205
+ workflow: options.workflow,
8206
+ base: options.base
8207
+ }),
8208
+ options
8209
+ );
8210
+ });
7833
8211
  const validate = program.command("validate").description("Run optional Truthmark workflow helper validators from the installed CLI.");
7834
8212
  addJsonOption(
7835
8213
  validate.command("sync-report").description("Validate a Truth Sync report file.").argument("<report-file>", "Truth Sync report file")