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