truthmark 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +4 -7
- package/README.es.md +4 -7
- package/README.md +4 -7
- package/README.ru.md +4 -7
- package/README.zh.md +4 -7
- package/dist/main.js +1316 -649
- package/dist/main.js.map +1 -1
- package/package.json +4 -1
package/dist/main.js
CHANGED
|
@@ -284,38 +284,11 @@ var truthmarkConfigSchema = {
|
|
|
284
284
|
truthmark: {
|
|
285
285
|
type: "object",
|
|
286
286
|
additionalProperties: false,
|
|
287
|
-
required: ["workspace", "
|
|
287
|
+
required: ["workspace", "generated"],
|
|
288
288
|
properties: {
|
|
289
289
|
workspace: {
|
|
290
290
|
type: "string"
|
|
291
291
|
},
|
|
292
|
-
routes: {
|
|
293
|
-
type: "object",
|
|
294
|
-
additionalProperties: false,
|
|
295
|
-
required: ["index", "areas", "default_area", "max_delegation_depth"],
|
|
296
|
-
properties: {
|
|
297
|
-
index: { type: "string" },
|
|
298
|
-
areas: { type: "string" },
|
|
299
|
-
default_area: { type: "string" },
|
|
300
|
-
max_delegation_depth: { type: "integer", const: 1 }
|
|
301
|
-
}
|
|
302
|
-
},
|
|
303
|
-
truth: {
|
|
304
|
-
type: "object",
|
|
305
|
-
additionalProperties: false,
|
|
306
|
-
required: ["root"],
|
|
307
|
-
properties: {
|
|
308
|
-
root: { type: "string" }
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
templates: {
|
|
312
|
-
type: "object",
|
|
313
|
-
additionalProperties: false,
|
|
314
|
-
required: ["root"],
|
|
315
|
-
properties: {
|
|
316
|
-
root: { type: "string" }
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
292
|
generated: {
|
|
320
293
|
type: "object",
|
|
321
294
|
additionalProperties: false,
|
|
@@ -375,33 +348,29 @@ var truthmarkConfigSchema = {
|
|
|
375
348
|
// src/config/defaults.ts
|
|
376
349
|
var DEFAULT_TRUTHMARK_WORKSPACE = {
|
|
377
350
|
workspace: "docs/truthmark",
|
|
378
|
-
routes: {
|
|
379
|
-
index: "routes/areas.md",
|
|
380
|
-
areas: "routes/areas",
|
|
381
|
-
default_area: "repository",
|
|
382
|
-
max_delegation_depth: 1
|
|
383
|
-
},
|
|
384
|
-
truth: {
|
|
385
|
-
root: "truth"
|
|
386
|
-
},
|
|
387
|
-
templates: {
|
|
388
|
-
root: "templates"
|
|
389
|
-
},
|
|
390
351
|
generated: {
|
|
391
352
|
portal: {
|
|
392
353
|
enabled: false
|
|
393
354
|
}
|
|
394
355
|
}
|
|
395
356
|
};
|
|
357
|
+
var DERIVED_TRUTHMARK_PATHS = {
|
|
358
|
+
routesIndex: "routes/areas.md",
|
|
359
|
+
routeAreasRoot: "routes/areas",
|
|
360
|
+
defaultArea: "repository",
|
|
361
|
+
maxDelegationDepth: 1,
|
|
362
|
+
productTruthRoot: "product",
|
|
363
|
+
engineeringTruthRoot: "engineering",
|
|
364
|
+
templatesRoot: "templates",
|
|
365
|
+
portalOutput: "generated/portal",
|
|
366
|
+
portalTemplate: "templates/portal.html"
|
|
367
|
+
};
|
|
396
368
|
var DEFAULT_INSTRUCTION_TARGETS = ["AGENTS.md"];
|
|
397
369
|
var createDefaultRawConfig = () => ({
|
|
398
370
|
version: 2,
|
|
399
371
|
platforms: [...DEFAULT_PLATFORMS],
|
|
400
372
|
truthmark: {
|
|
401
373
|
workspace: DEFAULT_TRUTHMARK_WORKSPACE.workspace,
|
|
402
|
-
routes: { ...DEFAULT_TRUTHMARK_WORKSPACE.routes },
|
|
403
|
-
truth: { ...DEFAULT_TRUTHMARK_WORKSPACE.truth },
|
|
404
|
-
templates: { ...DEFAULT_TRUTHMARK_WORKSPACE.templates },
|
|
405
374
|
generated: {
|
|
406
375
|
portal: { ...DEFAULT_TRUTHMARK_WORKSPACE.generated.portal }
|
|
407
376
|
}
|
|
@@ -409,7 +378,7 @@ var createDefaultRawConfig = () => ({
|
|
|
409
378
|
instruction_targets: [...DEFAULT_INSTRUCTION_TARGETS],
|
|
410
379
|
frontmatter: {
|
|
411
380
|
required: [],
|
|
412
|
-
recommended: ["status", "
|
|
381
|
+
recommended: ["status", "last_reviewed"]
|
|
413
382
|
},
|
|
414
383
|
ignore: ["node_modules/**", "vendor/**", "dist/**", "build/**"]
|
|
415
384
|
});
|
|
@@ -419,20 +388,24 @@ var createDefaultConfig = () => ({
|
|
|
419
388
|
truthmark: {
|
|
420
389
|
workspace: DEFAULT_TRUTHMARK_WORKSPACE.workspace,
|
|
421
390
|
routes: {
|
|
422
|
-
index:
|
|
423
|
-
areas:
|
|
424
|
-
defaultArea:
|
|
425
|
-
maxDelegationDepth:
|
|
391
|
+
index: DERIVED_TRUTHMARK_PATHS.routesIndex,
|
|
392
|
+
areas: DERIVED_TRUTHMARK_PATHS.routeAreasRoot,
|
|
393
|
+
defaultArea: DERIVED_TRUTHMARK_PATHS.defaultArea,
|
|
394
|
+
maxDelegationDepth: DERIVED_TRUTHMARK_PATHS.maxDelegationDepth
|
|
395
|
+
},
|
|
396
|
+
truth: {
|
|
397
|
+
productRoot: DERIVED_TRUTHMARK_PATHS.productTruthRoot,
|
|
398
|
+
engineeringRoot: DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
|
|
426
399
|
},
|
|
427
|
-
|
|
428
|
-
templates: { root: DEFAULT_TRUTHMARK_WORKSPACE.templates.root },
|
|
400
|
+
templates: { root: DERIVED_TRUTHMARK_PATHS.templatesRoot },
|
|
429
401
|
generated: {
|
|
430
402
|
portal: { ...DEFAULT_TRUTHMARK_WORKSPACE.generated.portal }
|
|
431
403
|
},
|
|
432
404
|
paths: {
|
|
433
405
|
routesIndex: "docs/truthmark/routes/areas.md",
|
|
434
406
|
routeAreasRoot: "docs/truthmark/routes/areas",
|
|
435
|
-
|
|
407
|
+
productTruthRoot: "docs/truthmark/product",
|
|
408
|
+
engineeringTruthRoot: "docs/truthmark/engineering",
|
|
436
409
|
templatesRoot: "docs/truthmark/templates",
|
|
437
410
|
portalOutput: "docs/truthmark/generated/portal",
|
|
438
411
|
portalTemplate: "docs/truthmark/templates/portal.html"
|
|
@@ -440,14 +413,15 @@ var createDefaultConfig = () => ({
|
|
|
440
413
|
controlledPaths: [
|
|
441
414
|
"docs/truthmark/routes/areas.md",
|
|
442
415
|
"docs/truthmark/routes/areas/**/*.md",
|
|
443
|
-
"docs/truthmark/
|
|
416
|
+
"docs/truthmark/product/**/*.md",
|
|
417
|
+
"docs/truthmark/engineering/**/*.md",
|
|
444
418
|
"docs/truthmark/templates/*.md"
|
|
445
419
|
]
|
|
446
420
|
},
|
|
447
421
|
instructionTargets: [...DEFAULT_INSTRUCTION_TARGETS],
|
|
448
422
|
frontmatter: {
|
|
449
423
|
required: [],
|
|
450
|
-
recommended: ["status", "
|
|
424
|
+
recommended: ["status", "last_reviewed"]
|
|
451
425
|
},
|
|
452
426
|
ignore: ["node_modules/**", "vendor/**", "dist/**", "build/**"]
|
|
453
427
|
});
|
|
@@ -455,14 +429,23 @@ var createDefaultConfig = () => ({
|
|
|
455
429
|
// src/routing/areas.ts
|
|
456
430
|
import { parse } from "yaml";
|
|
457
431
|
var TRUTH_DOCUMENT_KINDS = [
|
|
458
|
-
"
|
|
459
|
-
"
|
|
460
|
-
"
|
|
461
|
-
"workflow",
|
|
462
|
-
"
|
|
463
|
-
"
|
|
432
|
+
"product-capability",
|
|
433
|
+
"engineering-behavior",
|
|
434
|
+
"engineering-contract",
|
|
435
|
+
"engineering-workflow",
|
|
436
|
+
"engineering-architecture",
|
|
437
|
+
"engineering-operations",
|
|
438
|
+
"engineering-test-behavior"
|
|
464
439
|
];
|
|
465
|
-
var
|
|
440
|
+
var uniqueSorted = (values) => [...new Set(values)].sort();
|
|
441
|
+
var mergeTruthDocumentEntryRelationships = (first, second) => ({
|
|
442
|
+
...first,
|
|
443
|
+
realizedBy: uniqueSorted([...first.realizedBy, ...second.realizedBy]),
|
|
444
|
+
realizes: uniqueSorted([...first.realizes, ...second.realizes]),
|
|
445
|
+
dependsOn: uniqueSorted([...first.dependsOn, ...second.dependsOn])
|
|
446
|
+
});
|
|
447
|
+
var DEFAULT_PRODUCT_TRUTH_DOCS_ROOT = "docs/truthmark/product";
|
|
448
|
+
var DEFAULT_ENGINEERING_TRUTH_DOCS_ROOT = "docs/truthmark/engineering";
|
|
466
449
|
var slugify = (value) => {
|
|
467
450
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
468
451
|
};
|
|
@@ -482,12 +465,54 @@ var isTruthDocumentKind = (value) => {
|
|
|
482
465
|
};
|
|
483
466
|
var inferTruthDocumentKindFromPath = (documentPath, options = {}) => {
|
|
484
467
|
const normalizedPath = documentPath.replaceAll("\\", "/");
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
468
|
+
const productTruthRoot = (options.productTruthRoot ?? DEFAULT_PRODUCT_TRUTH_DOCS_ROOT)?.replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
469
|
+
const engineeringTruthRoot = (options.engineeringTruthRoot ?? options.truthDocsRoot ?? DEFAULT_ENGINEERING_TRUTH_DOCS_ROOT)?.replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
470
|
+
if (productTruthRoot && normalizedPath.startsWith(`${productTruthRoot}/`)) {
|
|
471
|
+
return "product-capability";
|
|
472
|
+
}
|
|
473
|
+
if (engineeringTruthRoot && normalizedPath.startsWith(`${engineeringTruthRoot}/`)) {
|
|
474
|
+
if (normalizedPath.includes("/contracts/")) return "engineering-contract";
|
|
475
|
+
if (normalizedPath.includes("/workflows/")) return "engineering-workflow";
|
|
476
|
+
if (normalizedPath.includes("/architecture/"))
|
|
477
|
+
return "engineering-architecture";
|
|
478
|
+
if (normalizedPath.includes("/operations/"))
|
|
479
|
+
return "engineering-operations";
|
|
480
|
+
if (normalizedPath.includes("/tests/")) return "engineering-test-behavior";
|
|
481
|
+
return "engineering-behavior";
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
484
|
+
};
|
|
485
|
+
var inferTruthDocumentLaneFromPath = (documentPath, options = {}) => {
|
|
486
|
+
const normalizedPath = documentPath.replaceAll("\\", "/");
|
|
487
|
+
const productTruthRoot = (options.productTruthRoot ?? DEFAULT_PRODUCT_TRUTH_DOCS_ROOT).replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
488
|
+
const engineeringTruthRoot = (options.engineeringTruthRoot ?? options.truthDocsRoot ?? DEFAULT_ENGINEERING_TRUTH_DOCS_ROOT).replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
489
|
+
if (normalizedPath.startsWith(`${productTruthRoot}/`)) {
|
|
490
|
+
return "product";
|
|
491
|
+
}
|
|
492
|
+
if (normalizedPath.startsWith(`${engineeringTruthRoot}/`)) {
|
|
493
|
+
return "engineering";
|
|
488
494
|
}
|
|
489
495
|
return null;
|
|
490
496
|
};
|
|
497
|
+
var laneForTruthDocumentKind = (kind) => {
|
|
498
|
+
return kind.startsWith("product-") ? "product" : "engineering";
|
|
499
|
+
};
|
|
500
|
+
var docTypeForTruthDocumentKind = (kind) => {
|
|
501
|
+
if (kind.startsWith("product-")) {
|
|
502
|
+
return "product";
|
|
503
|
+
}
|
|
504
|
+
return kind.slice("engineering-".length);
|
|
505
|
+
};
|
|
506
|
+
var parseStringListField = (rawEntry, field) => {
|
|
507
|
+
if (!rawEntry || typeof rawEntry !== "object" || !(field in rawEntry)) {
|
|
508
|
+
return [];
|
|
509
|
+
}
|
|
510
|
+
const value = rawEntry[field];
|
|
511
|
+
if (!Array.isArray(value)) {
|
|
512
|
+
return [];
|
|
513
|
+
}
|
|
514
|
+
return value.filter((entry) => typeof entry === "string");
|
|
515
|
+
};
|
|
491
516
|
var findTruthDocumentsYamlFenceRange = (sectionLines) => {
|
|
492
517
|
const trimmedLines = sectionLines.map((line) => line.trim());
|
|
493
518
|
const openingFenceIndex = trimmedLines.findIndex(
|
|
@@ -509,6 +534,7 @@ var parseTruthDocumentsFromList = (sectionLines, areaName, options) => {
|
|
|
509
534
|
const truthDocuments = parseListSection(sectionLines);
|
|
510
535
|
const truthDocumentEntries = truthDocuments.map((documentPath) => {
|
|
511
536
|
const inferredKind = inferTruthDocumentKindFromPath(documentPath, options);
|
|
537
|
+
const inferredLane = inferTruthDocumentLaneFromPath(documentPath, options);
|
|
512
538
|
if (!inferredKind) {
|
|
513
539
|
diagnostics.push(
|
|
514
540
|
createAreaDiagnostic(
|
|
@@ -520,8 +546,13 @@ var parseTruthDocumentsFromList = (sectionLines, areaName, options) => {
|
|
|
520
546
|
}
|
|
521
547
|
return {
|
|
522
548
|
path: documentPath,
|
|
523
|
-
kind: inferredKind ?? "behavior",
|
|
524
|
-
kindSource: inferredKind ? "inferred" : "defaulted"
|
|
549
|
+
kind: inferredKind ?? "engineering-behavior",
|
|
550
|
+
kindSource: inferredKind ? "inferred" : "defaulted",
|
|
551
|
+
lane: inferredLane ?? "engineering",
|
|
552
|
+
laneSource: inferredLane ? "inferred" : "defaulted",
|
|
553
|
+
realizedBy: [],
|
|
554
|
+
realizes: [],
|
|
555
|
+
dependsOn: []
|
|
525
556
|
};
|
|
526
557
|
});
|
|
527
558
|
return {
|
|
@@ -530,7 +561,7 @@ var parseTruthDocumentsFromList = (sectionLines, areaName, options) => {
|
|
|
530
561
|
diagnostics
|
|
531
562
|
};
|
|
532
563
|
};
|
|
533
|
-
var parseTruthDocumentsFromYaml = (sectionLines, areaName) => {
|
|
564
|
+
var parseTruthDocumentsFromYaml = (sectionLines, areaName, options) => {
|
|
534
565
|
const yamlFenceRange = findTruthDocumentsYamlFenceRange(sectionLines);
|
|
535
566
|
if (!yamlFenceRange) {
|
|
536
567
|
return {
|
|
@@ -587,21 +618,31 @@ var parseTruthDocumentsFromYaml = (sectionLines, areaName) => {
|
|
|
587
618
|
const diagnostics = [];
|
|
588
619
|
const truthDocumentEntries = [];
|
|
589
620
|
for (const rawEntry of rawEntries) {
|
|
590
|
-
const
|
|
621
|
+
const path14 = rawEntry && typeof rawEntry === "object" && "path" in rawEntry ? rawEntry.path : null;
|
|
591
622
|
const kind = rawEntry && typeof rawEntry === "object" && "kind" in rawEntry ? rawEntry.kind : null;
|
|
592
|
-
|
|
623
|
+
const lane = rawEntry && typeof rawEntry === "object" && "lane" in rawEntry ? rawEntry.lane : null;
|
|
624
|
+
const inferredKind = typeof path14 === "string" ? inferTruthDocumentKindFromPath(path14, options) : null;
|
|
625
|
+
const inferredLane = typeof path14 === "string" ? inferTruthDocumentLaneFromPath(path14, options) : null;
|
|
626
|
+
const normalizedKind = isTruthDocumentKind(kind) ? kind : inferredKind;
|
|
627
|
+
const normalizedLane = lane === "product" || lane === "engineering" ? lane : normalizedKind ? laneForTruthDocumentKind(normalizedKind) : inferredLane;
|
|
628
|
+
if (typeof path14 !== "string" || path14.trim().length === 0 || !normalizedKind || !normalizedLane) {
|
|
593
629
|
diagnostics.push(
|
|
594
630
|
createAreaDiagnostic(
|
|
595
|
-
`Area ${areaName} truth_documents entries must include non-empty path
|
|
631
|
+
`Area ${areaName} truth_documents entries must include non-empty path plus valid lane and kind fields.`,
|
|
596
632
|
areaName
|
|
597
633
|
)
|
|
598
634
|
);
|
|
599
635
|
continue;
|
|
600
636
|
}
|
|
601
637
|
truthDocumentEntries.push({
|
|
602
|
-
path:
|
|
603
|
-
kind,
|
|
604
|
-
kindSource: "explicit"
|
|
638
|
+
path: path14.trim(),
|
|
639
|
+
kind: normalizedKind,
|
|
640
|
+
kindSource: isTruthDocumentKind(kind) ? "explicit" : "inferred",
|
|
641
|
+
lane: normalizedLane,
|
|
642
|
+
laneSource: lane === "product" || lane === "engineering" ? "explicit" : "inferred",
|
|
643
|
+
realizedBy: parseStringListField(rawEntry, "realized_by"),
|
|
644
|
+
realizes: parseStringListField(rawEntry, "realizes"),
|
|
645
|
+
dependsOn: parseStringListField(rawEntry, "depends_on")
|
|
605
646
|
});
|
|
606
647
|
}
|
|
607
648
|
return {
|
|
@@ -615,7 +656,11 @@ var parseTruthDocumentsSection = (sectionLines, areaName, options) => {
|
|
|
615
656
|
if (!yamlFenceRange) {
|
|
616
657
|
return parseTruthDocumentsFromList(sectionLines, areaName, options);
|
|
617
658
|
}
|
|
618
|
-
const yamlResult = parseTruthDocumentsFromYaml(
|
|
659
|
+
const yamlResult = parseTruthDocumentsFromYaml(
|
|
660
|
+
sectionLines,
|
|
661
|
+
areaName,
|
|
662
|
+
options
|
|
663
|
+
);
|
|
619
664
|
if (yamlResult.diagnostics.length > 0 || yamlFenceRange.closingFenceIndex === null) {
|
|
620
665
|
return yamlResult;
|
|
621
666
|
}
|
|
@@ -697,8 +742,9 @@ var parseAreasMarkdown = (source, options = {}) => {
|
|
|
697
742
|
for (const line of lines) {
|
|
698
743
|
const areaHeadingMatch = line.match(/^\s{0,3}##\s+(.*)$/u);
|
|
699
744
|
if (areaHeadingMatch) {
|
|
745
|
+
const heading = areaHeadingMatch[1]?.trim() ?? null;
|
|
700
746
|
flushArea();
|
|
701
|
-
currentAreaName =
|
|
747
|
+
currentAreaName = heading === "Source References" ? null : heading;
|
|
702
748
|
continue;
|
|
703
749
|
}
|
|
704
750
|
if (!currentAreaName) {
|
|
@@ -726,7 +772,13 @@ var parseAreasMarkdown = (source, options = {}) => {
|
|
|
726
772
|
|
|
727
773
|
// src/truth/docs.ts
|
|
728
774
|
var resolveTruthDocsRoot = (config) => {
|
|
729
|
-
return config.truthmark.paths.
|
|
775
|
+
return config.truthmark.paths.engineeringTruthRoot;
|
|
776
|
+
};
|
|
777
|
+
var resolveProductTruthRoot = (config) => {
|
|
778
|
+
return config.truthmark.paths.productTruthRoot;
|
|
779
|
+
};
|
|
780
|
+
var resolveEngineeringTruthRoot = (config) => {
|
|
781
|
+
return config.truthmark.paths.engineeringTruthRoot;
|
|
730
782
|
};
|
|
731
783
|
|
|
732
784
|
// src/templates/init-files.ts
|
|
@@ -737,7 +789,25 @@ var currentDate = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
|
737
789
|
var resolveRelativePath = (fromPath, toPath) => {
|
|
738
790
|
return asRelativePath(path3.relative(path3.dirname(fromPath), toPath));
|
|
739
791
|
};
|
|
740
|
-
var truthRoot =
|
|
792
|
+
var truthRoot = resolveEngineeringTruthRoot;
|
|
793
|
+
var renderLaneRootReadmeSummary = (lane) => {
|
|
794
|
+
if (lane === "product") {
|
|
795
|
+
return [
|
|
796
|
+
"Product truth owns capability promises, boundaries, decisions, and acceptance criteria.",
|
|
797
|
+
"Product lane docs state what must be true, why it matters, and what success means."
|
|
798
|
+
].join(" ");
|
|
799
|
+
}
|
|
800
|
+
return [
|
|
801
|
+
"Engineering truth owns current realization, contracts, architecture, workflows, operations, and tests.",
|
|
802
|
+
"Engineering lane docs describe how the repository currently implements and operates the behavior."
|
|
803
|
+
].join(" ");
|
|
804
|
+
};
|
|
805
|
+
var renderLaneRootLeafDocGuidance = (lane) => {
|
|
806
|
+
if (lane === "product") {
|
|
807
|
+
return "README.md files are indexes, not Truth Sync targets. Keep product truth in bounded capability docs.";
|
|
808
|
+
}
|
|
809
|
+
return "README.md files are indexes, not Truth Sync targets. Keep engineering truth in bounded behavior, contract, architecture, workflow, operations, and test docs.";
|
|
810
|
+
};
|
|
741
811
|
var renderConfigTemplate = () => {
|
|
742
812
|
return stringify(createDefaultRawConfig());
|
|
743
813
|
};
|
|
@@ -757,8 +827,6 @@ var renderHierarchicalAreasIndexTemplate = (config) => {
|
|
|
757
827
|
"status: active",
|
|
758
828
|
"doc_type: route-index",
|
|
759
829
|
`last_reviewed: ${currentDate()}`,
|
|
760
|
-
"source_of_truth:",
|
|
761
|
-
` - ${sourceOfTruth}`,
|
|
762
830
|
"---",
|
|
763
831
|
"",
|
|
764
832
|
"# Truthmark Areas",
|
|
@@ -774,6 +842,10 @@ var renderHierarchicalAreasIndexTemplate = (config) => {
|
|
|
774
842
|
"Update truth when:",
|
|
775
843
|
"- behavior changes affect the routed truth documents",
|
|
776
844
|
"- API contracts or current feature behavior changes",
|
|
845
|
+
"",
|
|
846
|
+
"## Source References",
|
|
847
|
+
"",
|
|
848
|
+
`- ${sourceOfTruth}`,
|
|
777
849
|
""
|
|
778
850
|
].join("\n");
|
|
779
851
|
};
|
|
@@ -783,14 +855,15 @@ var renderChildAreaTemplate = (config) => {
|
|
|
783
855
|
const truthDocsRoot = truthRoot(config);
|
|
784
856
|
const leafTruthDoc = `${truthDocsRoot}/${defaultArea}/overview.md`;
|
|
785
857
|
const templatePath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
|
|
786
|
-
const sourceOfTruth = resolveRelativePath(
|
|
858
|
+
const sourceOfTruth = resolveRelativePath(
|
|
859
|
+
templatePath,
|
|
860
|
+
".truthmark/config.yml"
|
|
861
|
+
);
|
|
787
862
|
return [
|
|
788
863
|
"---",
|
|
789
864
|
"status: active",
|
|
790
865
|
"doc_type: area-route",
|
|
791
866
|
`last_reviewed: ${currentDate()}`,
|
|
792
|
-
"source_of_truth:",
|
|
793
|
-
` - ${sourceOfTruth}`,
|
|
794
867
|
"---",
|
|
795
868
|
"",
|
|
796
869
|
`# ${title} Areas`,
|
|
@@ -801,7 +874,8 @@ var renderChildAreaTemplate = (config) => {
|
|
|
801
874
|
"```yaml",
|
|
802
875
|
"truth_documents:",
|
|
803
876
|
` - path: ${leafTruthDoc}`,
|
|
804
|
-
" kind: behavior",
|
|
877
|
+
" kind: engineering-behavior",
|
|
878
|
+
" lane: engineering",
|
|
805
879
|
"```",
|
|
806
880
|
"",
|
|
807
881
|
"Code surface:",
|
|
@@ -809,11 +883,15 @@ var renderChildAreaTemplate = (config) => {
|
|
|
809
883
|
"",
|
|
810
884
|
"Update truth when:",
|
|
811
885
|
"- behavior changes affect repository truth",
|
|
886
|
+
"",
|
|
887
|
+
"## Source References",
|
|
888
|
+
"",
|
|
889
|
+
`- ${sourceOfTruth}`,
|
|
812
890
|
""
|
|
813
891
|
].join("\n");
|
|
814
892
|
};
|
|
815
|
-
var renderTruthRootReadmeTemplate = (config = createDefaultConfig()) => {
|
|
816
|
-
const templatePath = `${
|
|
893
|
+
var renderTruthRootReadmeTemplate = (config = createDefaultConfig(), lane = "engineering") => {
|
|
894
|
+
const templatePath = `${lane === "product" ? resolveProductTruthRoot(config) : resolveEngineeringTruthRoot(config)}/README.md`;
|
|
817
895
|
const sourceOfTruth = resolveRelativePath(
|
|
818
896
|
templatePath,
|
|
819
897
|
config.truthmark.paths.routesIndex
|
|
@@ -823,15 +901,19 @@ var renderTruthRootReadmeTemplate = (config = createDefaultConfig()) => {
|
|
|
823
901
|
"status: active",
|
|
824
902
|
"doc_type: index",
|
|
825
903
|
`last_reviewed: ${currentDate()}`,
|
|
826
|
-
"source_of_truth:",
|
|
827
|
-
` - ${sourceOfTruth}`,
|
|
828
904
|
"---",
|
|
829
905
|
"",
|
|
830
906
|
"# Truth Docs",
|
|
831
907
|
"",
|
|
832
908
|
"This directory is an index for current truth docs organized by the configured Truthmark hierarchy.",
|
|
833
909
|
"",
|
|
834
|
-
|
|
910
|
+
renderLaneRootReadmeSummary(lane),
|
|
911
|
+
"",
|
|
912
|
+
renderLaneRootLeafDocGuidance(lane),
|
|
913
|
+
"",
|
|
914
|
+
"## Source References",
|
|
915
|
+
"",
|
|
916
|
+
`- ${sourceOfTruth}`,
|
|
835
917
|
""
|
|
836
918
|
].join("\n");
|
|
837
919
|
};
|
|
@@ -848,8 +930,6 @@ var renderTruthDomainReadmeTemplate = (config) => {
|
|
|
848
930
|
"status: active",
|
|
849
931
|
"doc_type: index",
|
|
850
932
|
`last_reviewed: ${currentDate()}`,
|
|
851
|
-
"source_of_truth:",
|
|
852
|
-
` - ${sourceOfTruth}`,
|
|
853
933
|
"---",
|
|
854
934
|
"",
|
|
855
935
|
`# ${title} Truth Docs`,
|
|
@@ -861,10 +941,14 @@ var renderTruthDomainReadmeTemplate = (config) => {
|
|
|
861
941
|
"Current leaf docs:",
|
|
862
942
|
"",
|
|
863
943
|
"- [Overview](overview.md)",
|
|
944
|
+
"",
|
|
945
|
+
"## Source References",
|
|
946
|
+
"",
|
|
947
|
+
`- ${sourceOfTruth}`,
|
|
864
948
|
""
|
|
865
949
|
].join("\n");
|
|
866
950
|
};
|
|
867
|
-
var BEHAVIOR_DOC_TEMPLATE_PATH = "docs/truthmark/templates/behavior
|
|
951
|
+
var BEHAVIOR_DOC_TEMPLATE_PATH = "docs/truthmark/templates/engineering-behavior.md";
|
|
868
952
|
var renderTemplateSection = (section) => {
|
|
869
953
|
return [
|
|
870
954
|
section.heading,
|
|
@@ -928,24 +1012,68 @@ var parseTemplateSections = (template) => {
|
|
|
928
1012
|
sections
|
|
929
1013
|
};
|
|
930
1014
|
};
|
|
1015
|
+
var LEGACY_MANAGED_TEMPLATE_HEADINGS = /* @__PURE__ */ new Map([
|
|
1016
|
+
["## Current Behavior", "## Current Implementation Behavior"],
|
|
1017
|
+
["## Source Evidence", "## Source References"]
|
|
1018
|
+
]);
|
|
1019
|
+
var resolveManagedTemplateHeading = (heading) => {
|
|
1020
|
+
return LEGACY_MANAGED_TEMPLATE_HEADINGS.get(heading) ?? heading;
|
|
1021
|
+
};
|
|
1022
|
+
var stripManagedFrontmatterFields = (preamble) => {
|
|
1023
|
+
if (!preamble.startsWith("---\n")) {
|
|
1024
|
+
return preamble;
|
|
1025
|
+
}
|
|
1026
|
+
const lines = preamble.split("\n");
|
|
1027
|
+
const closingIndex = lines.findIndex(
|
|
1028
|
+
(line, index) => index > 0 && line.trim() === "---"
|
|
1029
|
+
);
|
|
1030
|
+
if (closingIndex < 0) {
|
|
1031
|
+
return preamble;
|
|
1032
|
+
}
|
|
1033
|
+
const fieldsToRemove = /* @__PURE__ */ new Set(["source_of_truth", "doc_type", "truth_lane"]);
|
|
1034
|
+
const keptFrontmatterLines = [];
|
|
1035
|
+
let skippingManagedField = false;
|
|
1036
|
+
for (const line of lines.slice(1, closingIndex)) {
|
|
1037
|
+
const keyMatch = /^([A-Za-z0-9_-]+):(\s|$)/u.exec(line);
|
|
1038
|
+
if (keyMatch) {
|
|
1039
|
+
skippingManagedField = fieldsToRemove.has(keyMatch[1] ?? "");
|
|
1040
|
+
}
|
|
1041
|
+
if (!skippingManagedField) {
|
|
1042
|
+
keptFrontmatterLines.push(line);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return [
|
|
1046
|
+
"---",
|
|
1047
|
+
...keptFrontmatterLines,
|
|
1048
|
+
"---",
|
|
1049
|
+
...lines.slice(closingIndex + 1)
|
|
1050
|
+
].join("\n").trimEnd();
|
|
1051
|
+
};
|
|
931
1052
|
var mergeTruthDocTemplate = (existingTemplate, defaultTemplate) => {
|
|
932
1053
|
if (existingTemplate.trim().length === 0) {
|
|
933
1054
|
return defaultTemplate;
|
|
934
1055
|
}
|
|
935
1056
|
const defaultParsed = parseTemplateSections(defaultTemplate);
|
|
936
1057
|
const existingParsed = parseTemplateSections(existingTemplate);
|
|
937
|
-
const defaultHeadings = new Set(
|
|
1058
|
+
const defaultHeadings = new Set(
|
|
1059
|
+
defaultParsed.sections.map((section) => section.heading)
|
|
1060
|
+
);
|
|
938
1061
|
const customBeforeDefault = /* @__PURE__ */ new Map();
|
|
939
1062
|
const trailingCustomSections = [];
|
|
940
1063
|
existingParsed.sections.forEach((section, index) => {
|
|
941
|
-
if (defaultHeadings.has(section.heading)) {
|
|
1064
|
+
if (defaultHeadings.has(resolveManagedTemplateHeading(section.heading))) {
|
|
942
1065
|
return;
|
|
943
1066
|
}
|
|
944
|
-
const nextDefaultSection = existingParsed.sections.slice(index + 1).find(
|
|
1067
|
+
const nextDefaultSection = existingParsed.sections.slice(index + 1).find(
|
|
1068
|
+
(candidate) => defaultHeadings.has(resolveManagedTemplateHeading(candidate.heading))
|
|
1069
|
+
);
|
|
945
1070
|
if (nextDefaultSection) {
|
|
946
|
-
const
|
|
1071
|
+
const nextDefaultHeading = resolveManagedTemplateHeading(
|
|
1072
|
+
nextDefaultSection.heading
|
|
1073
|
+
);
|
|
1074
|
+
const bucket = customBeforeDefault.get(nextDefaultHeading) ?? [];
|
|
947
1075
|
bucket.push(section);
|
|
948
|
-
customBeforeDefault.set(
|
|
1076
|
+
customBeforeDefault.set(nextDefaultHeading, bucket);
|
|
949
1077
|
return;
|
|
950
1078
|
}
|
|
951
1079
|
trailingCustomSections.push(section);
|
|
@@ -955,7 +1083,7 @@ var mergeTruthDocTemplate = (existingTemplate, defaultTemplate) => {
|
|
|
955
1083
|
section
|
|
956
1084
|
]);
|
|
957
1085
|
return [
|
|
958
|
-
existingParsed.preamble,
|
|
1086
|
+
stripManagedFrontmatterFields(existingParsed.preamble),
|
|
959
1087
|
...mergedSections.map((section) => section.block),
|
|
960
1088
|
...trailingCustomSections.map((section) => section.block),
|
|
961
1089
|
""
|
|
@@ -965,11 +1093,8 @@ var renderBehaviorDocTemplateFile = () => {
|
|
|
965
1093
|
return [
|
|
966
1094
|
"---",
|
|
967
1095
|
"status: active",
|
|
968
|
-
"
|
|
969
|
-
"truth_kind: behavior",
|
|
1096
|
+
"truth_kind: engineering-behavior",
|
|
970
1097
|
`last_reviewed: ${currentDate()}`,
|
|
971
|
-
"source_of_truth:",
|
|
972
|
-
" - {{source_of_truth}}",
|
|
973
1098
|
"---",
|
|
974
1099
|
"",
|
|
975
1100
|
"# {{title}}",
|
|
@@ -979,7 +1104,7 @@ var renderBehaviorDocTemplateFile = () => {
|
|
|
979
1104
|
"<!--",
|
|
980
1105
|
"State the user/system outcome this behavior protects and why it exists.",
|
|
981
1106
|
"Include the problem boundary and durable value; exclude roadmap, implementation plan, and historical narrative.",
|
|
982
|
-
"List the code, config, docs, or tests that support the claim in
|
|
1107
|
+
"List the code, config, docs, or tests that support the claim in Source References rather than prose-only assertion.",
|
|
983
1108
|
"-->",
|
|
984
1109
|
"",
|
|
985
1110
|
"{{purpose}}",
|
|
@@ -995,17 +1120,17 @@ var renderBehaviorDocTemplateFile = () => {
|
|
|
995
1120
|
"",
|
|
996
1121
|
"{{scope}}",
|
|
997
1122
|
"",
|
|
998
|
-
"This doc was created from the editable behavior
|
|
1123
|
+
"This doc was created from the editable engineering-behavior template at {{template_path}}.",
|
|
999
1124
|
"",
|
|
1000
|
-
"## Current Behavior",
|
|
1125
|
+
"## Current Implementation Behavior",
|
|
1001
1126
|
"",
|
|
1002
1127
|
"<!--",
|
|
1003
1128
|
"Describe only current implemented behavior in present tense.",
|
|
1004
1129
|
"Cover observable behavior, important defaults, and user/system-visible effects; exclude desired future behavior and speculative design.",
|
|
1005
|
-
"Every non-obvious claim should be checkable from
|
|
1130
|
+
"Every non-obvious claim should be checkable from Source References.",
|
|
1006
1131
|
"-->",
|
|
1007
1132
|
"",
|
|
1008
|
-
"{{
|
|
1133
|
+
"{{current_implementation_behavior}}",
|
|
1009
1134
|
"",
|
|
1010
1135
|
"## Core Rules",
|
|
1011
1136
|
"",
|
|
@@ -1034,14 +1159,23 @@ var renderBehaviorDocTemplateFile = () => {
|
|
|
1034
1159
|
"",
|
|
1035
1160
|
"{{contracts}}",
|
|
1036
1161
|
"",
|
|
1037
|
-
"## Product
|
|
1162
|
+
"## Product Truth Links",
|
|
1163
|
+
"",
|
|
1164
|
+
"<!--",
|
|
1165
|
+
"List product truth docs this engineering doc realizes; author canonical realizes links in route YAML, not doc frontmatter.",
|
|
1166
|
+
"Use 'None.' when this is purely internal engineering behavior.",
|
|
1167
|
+
"-->",
|
|
1168
|
+
"",
|
|
1169
|
+
"{{product_truth_links}}",
|
|
1170
|
+
"",
|
|
1171
|
+
"## Engineering Decisions",
|
|
1038
1172
|
"",
|
|
1039
1173
|
"<!--",
|
|
1040
1174
|
"Keep active decisions only, dated inline when added or changed.",
|
|
1041
1175
|
"Explain decisions that shape behavior, boundaries, rejected alternatives, or migration constraints; replace stale decisions instead of appending historical logs.",
|
|
1042
1176
|
"-->",
|
|
1043
1177
|
"",
|
|
1044
|
-
"{{
|
|
1178
|
+
"{{engineering_decisions}}",
|
|
1045
1179
|
"",
|
|
1046
1180
|
"## Rationale",
|
|
1047
1181
|
"",
|
|
@@ -1069,6 +1203,14 @@ var renderBehaviorDocTemplateFile = () => {
|
|
|
1069
1203
|
"-->",
|
|
1070
1204
|
"",
|
|
1071
1205
|
"{{maintenance_notes}}",
|
|
1206
|
+
"",
|
|
1207
|
+
"## Source References",
|
|
1208
|
+
"",
|
|
1209
|
+
"<!--",
|
|
1210
|
+
"List source files, tests, configs, generated templates, route files, or product instructions that support current claims.",
|
|
1211
|
+
"-->",
|
|
1212
|
+
"",
|
|
1213
|
+
"{{source_references}}",
|
|
1072
1214
|
""
|
|
1073
1215
|
].join("\n");
|
|
1074
1216
|
};
|
|
@@ -1076,17 +1218,30 @@ var sectionSpec = (heading, guidance, placeholder = titleToPlaceholder(heading))
|
|
|
1076
1218
|
var PURPOSE_SECTION = sectionSpec("## Purpose", [
|
|
1077
1219
|
"State the software-engineering outcome this document protects and why the documented surface exists.",
|
|
1078
1220
|
"Include durable value, impacted users/systems, and the problem boundary; exclude roadmap, implementation plans, and historical narrative.",
|
|
1079
|
-
"Keep claims traceable to
|
|
1221
|
+
"Keep claims traceable to Source References rather than prose-only assertion."
|
|
1080
1222
|
]);
|
|
1081
1223
|
var SCOPE_SECTION = sectionSpec("## Scope", [
|
|
1082
1224
|
"Define the one coherent surface this document owns, including actors, entrypoints, owned state/data, and handoffs to neighboring truth docs.",
|
|
1083
1225
|
"Call out important out-of-scope boundaries here or in Non-Goals; split the doc when it mixes distinct outcomes, lifecycles, contracts, or owners."
|
|
1084
1226
|
]);
|
|
1085
|
-
var PRODUCT_DECISIONS_SECTION = sectionSpec(
|
|
1086
|
-
"
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1227
|
+
var PRODUCT_DECISIONS_SECTION = sectionSpec(
|
|
1228
|
+
"## Product Decisions",
|
|
1229
|
+
[
|
|
1230
|
+
"Keep active decisions only, dated inline when added or changed.",
|
|
1231
|
+
"Capture decisions that shape behavior, interfaces, boundaries, compatibility, risk acceptance, or migration constraints.",
|
|
1232
|
+
"Replace stale decisions instead of appending historical logs."
|
|
1233
|
+
],
|
|
1234
|
+
"decision"
|
|
1235
|
+
);
|
|
1236
|
+
var ENGINEERING_DECISIONS_SECTION = sectionSpec(
|
|
1237
|
+
"## Engineering Decisions",
|
|
1238
|
+
[
|
|
1239
|
+
"Keep active engineering, architecture, contract, workflow, or operational decisions only, dated inline when added or changed.",
|
|
1240
|
+
"Do not restate product promises, product rationale, or business decisions here; link product truth instead.",
|
|
1241
|
+
"Replace stale decisions instead of appending historical logs."
|
|
1242
|
+
],
|
|
1243
|
+
"engineering_decisions"
|
|
1244
|
+
);
|
|
1090
1245
|
var RATIONALE_SECTION = sectionSpec("## Rationale", [
|
|
1091
1246
|
"Explain why the current behavior, structure, or contract is this way, including tradeoffs and constraints.",
|
|
1092
1247
|
"Tie rationale to evidence-backed facts and active decisions; do not use this as a changelog."
|
|
@@ -1099,15 +1254,19 @@ var MAINTENANCE_NOTES_SECTION = sectionSpec("## Maintenance Notes", [
|
|
|
1099
1254
|
"List related tests, routing cautions, migration notes, compatibility risks, evidence drift risks, and review triggers for future maintainers or agents.",
|
|
1100
1255
|
"Keep this operational and current-state focused, not historical."
|
|
1101
1256
|
]);
|
|
1102
|
-
var
|
|
1257
|
+
var SOURCE_REFERENCES_SECTION = sectionSpec(
|
|
1258
|
+
"## Source References",
|
|
1259
|
+
[
|
|
1260
|
+
"List source files, tests, configs, generated templates, route files, or product instructions that support current claims."
|
|
1261
|
+
],
|
|
1262
|
+
"source_references"
|
|
1263
|
+
);
|
|
1264
|
+
var renderTypedTruthDocTemplate = (truthKind, title, sections) => {
|
|
1103
1265
|
return [
|
|
1104
1266
|
"---",
|
|
1105
1267
|
"status: active",
|
|
1106
|
-
`doc_type: ${docType}`,
|
|
1107
1268
|
`truth_kind: ${truthKind}`,
|
|
1108
1269
|
`last_reviewed: ${currentDate()}`,
|
|
1109
|
-
"source_of_truth:",
|
|
1110
|
-
" - {{source_of_truth}}",
|
|
1111
1270
|
"---",
|
|
1112
1271
|
"",
|
|
1113
1272
|
`# ${title}`,
|
|
@@ -1115,14 +1274,77 @@ var renderTypedTruthDocTemplate = (truthKind, docType, title, sections) => {
|
|
|
1115
1274
|
...renderTemplateSection(PURPOSE_SECTION),
|
|
1116
1275
|
...renderTemplateSection(SCOPE_SECTION),
|
|
1117
1276
|
...sections.flatMap(renderTemplateSection),
|
|
1118
|
-
...renderTemplateSection(
|
|
1277
|
+
...renderTemplateSection(ENGINEERING_DECISIONS_SECTION),
|
|
1119
1278
|
...renderTemplateSection(RATIONALE_SECTION),
|
|
1120
1279
|
...renderTemplateSection(NON_GOALS_SECTION),
|
|
1121
|
-
...renderTemplateSection(MAINTENANCE_NOTES_SECTION)
|
|
1280
|
+
...renderTemplateSection(MAINTENANCE_NOTES_SECTION),
|
|
1281
|
+
...renderTemplateSection(SOURCE_REFERENCES_SECTION)
|
|
1282
|
+
].join("\n");
|
|
1283
|
+
};
|
|
1284
|
+
var CORE_LANE_INVARIANT = "Do not make product docs a summary of engineering docs. Do not make engineering docs a detailed version of product docs. Product truth says what must be true and why. Engineering truth says how the repository currently realizes it.";
|
|
1285
|
+
var renderProductTruthDocTemplate = (truthKind, title, sections, includeNonGoals) => {
|
|
1286
|
+
return [
|
|
1287
|
+
"---",
|
|
1288
|
+
"status: active",
|
|
1289
|
+
`truth_kind: ${truthKind}`,
|
|
1290
|
+
`last_reviewed: ${currentDate()}`,
|
|
1291
|
+
"---",
|
|
1292
|
+
"",
|
|
1293
|
+
`# ${title}`,
|
|
1294
|
+
"",
|
|
1295
|
+
"<!--",
|
|
1296
|
+
CORE_LANE_INVARIANT,
|
|
1297
|
+
"Product docs may cite code directly when code proves current product behavior, but keep implementation flow, renderer internals, CLI envelopes, and generated file inventories in engineering truth.",
|
|
1298
|
+
"-->",
|
|
1299
|
+
"",
|
|
1300
|
+
...sections.flatMap(renderTemplateSection),
|
|
1301
|
+
...renderTemplateSection(PRODUCT_DECISIONS_SECTION),
|
|
1302
|
+
...renderTemplateSection(
|
|
1303
|
+
sectionSpec(
|
|
1304
|
+
"## Engineering Realization Links",
|
|
1305
|
+
[
|
|
1306
|
+
"List engineering truth that realizes this product truth; author canonical realized_by links in route YAML, not doc frontmatter.",
|
|
1307
|
+
"Do not summarize those engineering docs."
|
|
1308
|
+
],
|
|
1309
|
+
"engineering_realization_links"
|
|
1310
|
+
)
|
|
1311
|
+
),
|
|
1312
|
+
...includeNonGoals ? renderTemplateSection(NON_GOALS_SECTION) : [],
|
|
1313
|
+
...renderTemplateSection(SOURCE_REFERENCES_SECTION)
|
|
1122
1314
|
].join("\n");
|
|
1123
1315
|
};
|
|
1316
|
+
var renderProductCapabilityDocTemplateFile = () => {
|
|
1317
|
+
return renderProductTruthDocTemplate(
|
|
1318
|
+
"product-capability",
|
|
1319
|
+
"{{title}}",
|
|
1320
|
+
[
|
|
1321
|
+
sectionSpec("## Capability Promise", [
|
|
1322
|
+
"State the single user-visible capability and what must be true for users or stakeholders.",
|
|
1323
|
+
"Do not describe implementation mechanics here."
|
|
1324
|
+
]),
|
|
1325
|
+
sectionSpec("## Users And Value", [
|
|
1326
|
+
"Describe who benefits from the capability and the durable value it protects.",
|
|
1327
|
+
"Tie claims to repository evidence, explicit user instruction, or current behavior."
|
|
1328
|
+
]),
|
|
1329
|
+
sectionSpec("## Capability Scope", [
|
|
1330
|
+
"Define what this capability includes and excludes, including product boundary constraints and adjacent systems.",
|
|
1331
|
+
"Capture important scope limits, ownership boundaries, and non-goal pointers here; keep technical contracts in engineering truth."
|
|
1332
|
+
]),
|
|
1333
|
+
sectionSpec("## Current Product Behavior", [
|
|
1334
|
+
"Describe current implemented user-visible behavior in present tense.",
|
|
1335
|
+
"Code files may appear in Source References when they directly prove current behavior."
|
|
1336
|
+
]),
|
|
1337
|
+
sectionSpec("## Acceptance Criteria", [
|
|
1338
|
+
"List observable criteria that show the capability promise is currently satisfied.",
|
|
1339
|
+
"Include criteria that review whether the capability stays within its stated scope and boundary.",
|
|
1340
|
+
"Use criteria that can be reviewed from repository evidence or explicit product instruction."
|
|
1341
|
+
])
|
|
1342
|
+
],
|
|
1343
|
+
true
|
|
1344
|
+
);
|
|
1345
|
+
};
|
|
1124
1346
|
var renderContractDocTemplateFile = () => {
|
|
1125
|
-
return renderTypedTruthDocTemplate("contract", "
|
|
1347
|
+
return renderTypedTruthDocTemplate("engineering-contract", "{{title}}", [
|
|
1126
1348
|
sectionSpec("## Contract Surface", [
|
|
1127
1349
|
"Identify the owned API, CLI, file format, event, protocol, permission boundary, or integration surface.",
|
|
1128
1350
|
"State consumers/producers, stability level, and the source files/tests that define the contract."
|
|
@@ -1150,7 +1372,7 @@ var renderContractDocTemplateFile = () => {
|
|
|
1150
1372
|
]);
|
|
1151
1373
|
};
|
|
1152
1374
|
var renderArchitectureDocTemplateFile = () => {
|
|
1153
|
-
return renderTypedTruthDocTemplate("architecture", "
|
|
1375
|
+
return renderTypedTruthDocTemplate("engineering-architecture", "{{title}}", [
|
|
1154
1376
|
sectionSpec("## System Role", [
|
|
1155
1377
|
"Describe the current architectural role of this subsystem/component in the larger system.",
|
|
1156
1378
|
"State the primary responsibilities, consumers, providers, and why this boundary exists now."
|
|
@@ -1178,7 +1400,7 @@ var renderArchitectureDocTemplateFile = () => {
|
|
|
1178
1400
|
]);
|
|
1179
1401
|
};
|
|
1180
1402
|
var renderWorkflowDocTemplateFile = () => {
|
|
1181
|
-
return renderTypedTruthDocTemplate("workflow", "
|
|
1403
|
+
return renderTypedTruthDocTemplate("engineering-workflow", "{{title}}", [
|
|
1182
1404
|
sectionSpec("## Triggers", [
|
|
1183
1405
|
"List events, commands, schedules, user actions, webhooks, or dependency signals that start this workflow.",
|
|
1184
1406
|
"Include preconditions, authorization requirements, debounce/coalescing behavior, and disabled states when applicable."
|
|
@@ -1206,7 +1428,7 @@ var renderWorkflowDocTemplateFile = () => {
|
|
|
1206
1428
|
]);
|
|
1207
1429
|
};
|
|
1208
1430
|
var renderOperationsDocTemplateFile = () => {
|
|
1209
|
-
return renderTypedTruthDocTemplate("operations", "
|
|
1431
|
+
return renderTypedTruthDocTemplate("engineering-operations", "{{title}}", [
|
|
1210
1432
|
sectionSpec("## Operational Surface", [
|
|
1211
1433
|
"Describe what operators, maintainers, or automated systems can observe or control for this surface.",
|
|
1212
1434
|
"Include commands, dashboards, alerts, runbooks, jobs, or operational APIs that define current operations."
|
|
@@ -1234,7 +1456,7 @@ var renderOperationsDocTemplateFile = () => {
|
|
|
1234
1456
|
]);
|
|
1235
1457
|
};
|
|
1236
1458
|
var renderTestBehaviorDocTemplateFile = () => {
|
|
1237
|
-
return renderTypedTruthDocTemplate("test-behavior", "
|
|
1459
|
+
return renderTypedTruthDocTemplate("engineering-test-behavior", "{{title}}", [
|
|
1238
1460
|
sectionSpec("## Test Surface", [
|
|
1239
1461
|
"Define the behavior, contract, architecture, or workflow surface these tests verify.",
|
|
1240
1462
|
"Link the canonical truth docs and code paths the tests are meant to protect."
|
|
@@ -1279,18 +1501,19 @@ var renderBehaviorLeafDocTemplate = (config, template = renderBehaviorDocTemplat
|
|
|
1279
1501
|
area: defaultArea,
|
|
1280
1502
|
contracts: "- External contracts should link to the nearest canonical contract doc when one exists.",
|
|
1281
1503
|
core_rules: "- Truth README files are indexes; behavior truth belongs in bounded leaf docs.",
|
|
1282
|
-
|
|
1283
|
-
|
|
1504
|
+
current_implementation_behavior: "- Document current behavior here when implementation changes make repository truth incomplete.",
|
|
1505
|
+
engineering_decisions: `- Decision (${today}): Truth README files are indexes; behavior truth belongs in bounded leaf docs.`,
|
|
1284
1506
|
flows_and_states: "- None beyond current behavior.",
|
|
1285
1507
|
maintenance_notes: "- Update this doc when routed implementation changes alter current behavior, rules, contracts, or decisions.",
|
|
1286
1508
|
non_goals: "- This doc is not a catch-all for unrelated repository behavior.",
|
|
1287
1509
|
purpose: `Describe why the default ${title.toLowerCase()} behavior surface exists and what outcome it protects.`,
|
|
1288
1510
|
rationale: "Bounded leaf docs keep agent context focused and prevent large products from accumulating unreviewable feature manuals.",
|
|
1511
|
+
product_truth_links: "- None.",
|
|
1289
1512
|
scope: `This bounded leaf truth doc owns the default ${title.toLowerCase()} behavior surface created by Truthmark.`,
|
|
1290
|
-
|
|
1513
|
+
source_references: `- ${sourceOfTruth}`,
|
|
1291
1514
|
template_path: BEHAVIOR_DOC_TEMPLATE_PATH,
|
|
1292
1515
|
title: `${title} Overview`,
|
|
1293
|
-
truth_kind: "behavior"
|
|
1516
|
+
truth_kind: "engineering-behavior"
|
|
1294
1517
|
});
|
|
1295
1518
|
};
|
|
1296
1519
|
|
|
@@ -1399,8 +1622,7 @@ var isUnsafeRepoRelativePath = (value) => {
|
|
|
1399
1622
|
var joinWorkspacePath = (workspace, childPath) => {
|
|
1400
1623
|
return normalizeRepoRelativePath(`${workspace}/${childPath}`);
|
|
1401
1624
|
};
|
|
1402
|
-
var portalOutputFor = (workspace) => joinWorkspacePath(workspace,
|
|
1403
|
-
var portalTemplateFor = (templatesRoot) => joinWorkspacePath(templatesRoot, "portal.html");
|
|
1625
|
+
var portalOutputFor = (workspace) => joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.portalOutput);
|
|
1404
1626
|
var pathsOverlap = (left, right) => {
|
|
1405
1627
|
const normalizedLeft = normalizeRepoRelativePath(left);
|
|
1406
1628
|
const normalizedRight = normalizeRepoRelativePath(right);
|
|
@@ -1444,12 +1666,6 @@ var unsupportedShapeDiagnostics = (parsedConfig, configPath) => {
|
|
|
1444
1666
|
var validateWorkspacePaths = (rawConfig, configPath) => {
|
|
1445
1667
|
const diagnostics = [];
|
|
1446
1668
|
const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
|
|
1447
|
-
const childPaths = [
|
|
1448
|
-
["truthmark.routes.index", rawConfig.truthmark.routes.index],
|
|
1449
|
-
["truthmark.routes.areas", rawConfig.truthmark.routes.areas],
|
|
1450
|
-
["truthmark.truth.root", rawConfig.truthmark.truth.root],
|
|
1451
|
-
["truthmark.templates.root", rawConfig.truthmark.templates.root]
|
|
1452
|
-
];
|
|
1453
1669
|
if (isUnsafeRepoRelativePath(rawConfig.truthmark.workspace) || FORBIDDEN_WORKSPACE_OVERLAPS.some((forbidden) => pathsOverlap(workspace, forbidden))) {
|
|
1454
1670
|
diagnostics.push(
|
|
1455
1671
|
toConfigDiagnostic(
|
|
@@ -1458,33 +1674,6 @@ var validateWorkspacePaths = (rawConfig, configPath) => {
|
|
|
1458
1674
|
)
|
|
1459
1675
|
);
|
|
1460
1676
|
}
|
|
1461
|
-
for (const [name, value] of childPaths) {
|
|
1462
|
-
if (isUnsafeRepoRelativePath(value)) {
|
|
1463
|
-
diagnostics.push(
|
|
1464
|
-
toConfigDiagnostic(
|
|
1465
|
-
`${name} must be a non-empty path relative to truthmark.workspace without absolute or parent traversal segments.`,
|
|
1466
|
-
configPath
|
|
1467
|
-
)
|
|
1468
|
-
);
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
const portalOutput = normalizeRepoRelativePath("generated/portal");
|
|
1472
|
-
const controlledWorkspaceChildren = [
|
|
1473
|
-
["truthmark.routes.index", rawConfig.truthmark.routes.index],
|
|
1474
|
-
["truthmark.routes.areas", rawConfig.truthmark.routes.areas],
|
|
1475
|
-
["truthmark.truth.root", rawConfig.truthmark.truth.root],
|
|
1476
|
-
["truthmark.templates.root", rawConfig.truthmark.templates.root]
|
|
1477
|
-
];
|
|
1478
|
-
for (const [name, value] of controlledWorkspaceChildren) {
|
|
1479
|
-
if (pathsOverlap(portalOutput, value)) {
|
|
1480
|
-
diagnostics.push(
|
|
1481
|
-
toConfigDiagnostic(
|
|
1482
|
-
`Truthmark Portal output ${portalOutputFor(workspace)} must not overlap ${name}; Portal output is generated and must stay outside controlled truth, routing, and template paths.`,
|
|
1483
|
-
configPath
|
|
1484
|
-
)
|
|
1485
|
-
);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
1677
|
for (const target of rawConfig.instruction_targets ?? DEFAULT_INSTRUCTION_TARGETS) {
|
|
1489
1678
|
if (isUnsafeRepoRelativePath(target) || pathsOverlap(workspace, target)) {
|
|
1490
1679
|
diagnostics.push(
|
|
@@ -1499,28 +1688,33 @@ var validateWorkspacePaths = (rawConfig, configPath) => {
|
|
|
1499
1688
|
};
|
|
1500
1689
|
var normalizeConfig = (rawConfig) => {
|
|
1501
1690
|
const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
|
|
1502
|
-
const routesIndex = joinWorkspacePath(workspace,
|
|
1503
|
-
const routeAreasRoot = joinWorkspacePath(workspace,
|
|
1504
|
-
const
|
|
1505
|
-
const
|
|
1691
|
+
const routesIndex = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.routesIndex);
|
|
1692
|
+
const routeAreasRoot = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.routeAreasRoot);
|
|
1693
|
+
const productTruthRoot = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.productTruthRoot);
|
|
1694
|
+
const engineeringTruthRoot = joinWorkspacePath(
|
|
1695
|
+
workspace,
|
|
1696
|
+
DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
|
|
1697
|
+
);
|
|
1698
|
+
const templatesRoot = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.templatesRoot);
|
|
1506
1699
|
const portalOutput = portalOutputFor(workspace);
|
|
1507
|
-
const portalTemplate =
|
|
1700
|
+
const portalTemplate = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.portalTemplate);
|
|
1508
1701
|
return {
|
|
1509
1702
|
version: rawConfig.version,
|
|
1510
1703
|
platforms: rawConfig.platforms ?? [...DEFAULT_PLATFORMS],
|
|
1511
1704
|
truthmark: {
|
|
1512
1705
|
workspace,
|
|
1513
1706
|
routes: {
|
|
1514
|
-
index:
|
|
1515
|
-
areas:
|
|
1516
|
-
defaultArea:
|
|
1517
|
-
maxDelegationDepth:
|
|
1707
|
+
index: DERIVED_TRUTHMARK_PATHS.routesIndex,
|
|
1708
|
+
areas: DERIVED_TRUTHMARK_PATHS.routeAreasRoot,
|
|
1709
|
+
defaultArea: DERIVED_TRUTHMARK_PATHS.defaultArea,
|
|
1710
|
+
maxDelegationDepth: DERIVED_TRUTHMARK_PATHS.maxDelegationDepth
|
|
1518
1711
|
},
|
|
1519
1712
|
truth: {
|
|
1520
|
-
|
|
1713
|
+
productRoot: DERIVED_TRUTHMARK_PATHS.productTruthRoot,
|
|
1714
|
+
engineeringRoot: DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
|
|
1521
1715
|
},
|
|
1522
1716
|
templates: {
|
|
1523
|
-
root:
|
|
1717
|
+
root: DERIVED_TRUTHMARK_PATHS.templatesRoot
|
|
1524
1718
|
},
|
|
1525
1719
|
generated: {
|
|
1526
1720
|
portal: {
|
|
@@ -1530,7 +1724,8 @@ var normalizeConfig = (rawConfig) => {
|
|
|
1530
1724
|
paths: {
|
|
1531
1725
|
routesIndex,
|
|
1532
1726
|
routeAreasRoot,
|
|
1533
|
-
|
|
1727
|
+
productTruthRoot,
|
|
1728
|
+
engineeringTruthRoot,
|
|
1534
1729
|
templatesRoot,
|
|
1535
1730
|
portalOutput,
|
|
1536
1731
|
portalTemplate
|
|
@@ -1538,7 +1733,8 @@ var normalizeConfig = (rawConfig) => {
|
|
|
1538
1733
|
controlledPaths: [
|
|
1539
1734
|
routesIndex,
|
|
1540
1735
|
`${routeAreasRoot}/**/*.md`,
|
|
1541
|
-
`${
|
|
1736
|
+
`${productTruthRoot}/**/*.md`,
|
|
1737
|
+
`${engineeringTruthRoot}/**/*.md`,
|
|
1542
1738
|
`${templatesRoot}/*.md`
|
|
1543
1739
|
]
|
|
1544
1740
|
},
|
|
@@ -1624,8 +1820,18 @@ var loadConfig = async (rootDir) => {
|
|
|
1624
1820
|
// src/init/hierarchy.ts
|
|
1625
1821
|
import fs5 from "fs/promises";
|
|
1626
1822
|
var truthRoot2 = resolveTruthDocsRoot;
|
|
1823
|
+
var BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-behavior.md";
|
|
1824
|
+
var CONTRACT_DOC_TEMPLATE_FILE_NAME = "engineering-contract.md";
|
|
1825
|
+
var ARCHITECTURE_DOC_TEMPLATE_FILE_NAME = "engineering-architecture.md";
|
|
1826
|
+
var WORKFLOW_DOC_TEMPLATE_FILE_NAME = "engineering-workflow.md";
|
|
1827
|
+
var OPERATIONS_DOC_TEMPLATE_FILE_NAME = "engineering-operations.md";
|
|
1828
|
+
var TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-test-behavior.md";
|
|
1829
|
+
var PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME = "product-capability.md";
|
|
1627
1830
|
var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePath) => {
|
|
1628
|
-
const rootIndexSource = await fs5.readFile(
|
|
1831
|
+
const rootIndexSource = await fs5.readFile(
|
|
1832
|
+
resolveRepoPath(rootDir, rootIndexPath),
|
|
1833
|
+
"utf8"
|
|
1834
|
+
);
|
|
1629
1835
|
const parsedRootIndex = parseAreasMarkdown(rootIndexSource);
|
|
1630
1836
|
return parsedRootIndex.areaFileReferences.some(
|
|
1631
1837
|
(areaReference) => areaReference.areaFiles.includes(childRoutePath)
|
|
@@ -1637,7 +1843,10 @@ var truthTemplatePath = (config, fileName) => {
|
|
|
1637
1843
|
var readBehaviorDocTemplate = async (rootDir, config) => {
|
|
1638
1844
|
try {
|
|
1639
1845
|
return await fs5.readFile(
|
|
1640
|
-
resolveRepoPath(
|
|
1846
|
+
resolveRepoPath(
|
|
1847
|
+
rootDir,
|
|
1848
|
+
truthTemplatePath(config, BEHAVIOR_DOC_TEMPLATE_FILE_NAME)
|
|
1849
|
+
),
|
|
1641
1850
|
"utf8"
|
|
1642
1851
|
);
|
|
1643
1852
|
} catch (error) {
|
|
@@ -1648,12 +1857,22 @@ var readBehaviorDocTemplate = async (rootDir, config) => {
|
|
|
1648
1857
|
}
|
|
1649
1858
|
};
|
|
1650
1859
|
var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTemplate) => {
|
|
1651
|
-
const seededResult = await ensureRepoFile(
|
|
1860
|
+
const seededResult = await ensureRepoFile(
|
|
1861
|
+
rootDir,
|
|
1862
|
+
templatePath,
|
|
1863
|
+
defaultTemplate
|
|
1864
|
+
);
|
|
1652
1865
|
if (seededResult.status !== "unchanged") {
|
|
1653
1866
|
return seededResult;
|
|
1654
1867
|
}
|
|
1655
|
-
const existingTemplate = await fs5.readFile(
|
|
1656
|
-
|
|
1868
|
+
const existingTemplate = await fs5.readFile(
|
|
1869
|
+
resolveRepoPath(rootDir, templatePath),
|
|
1870
|
+
"utf8"
|
|
1871
|
+
);
|
|
1872
|
+
const mergedTemplate = mergeTruthDocTemplate(
|
|
1873
|
+
existingTemplate,
|
|
1874
|
+
defaultTemplate
|
|
1875
|
+
);
|
|
1657
1876
|
return writeRepoFile(rootDir, templatePath, mergedTemplate);
|
|
1658
1877
|
};
|
|
1659
1878
|
var scaffoldHierarchy = async (rootDir, config) => {
|
|
@@ -1668,14 +1887,31 @@ var scaffoldHierarchy = async (rootDir, config) => {
|
|
|
1668
1887
|
renderHierarchicalAreasIndexTemplate(config)
|
|
1669
1888
|
)
|
|
1670
1889
|
);
|
|
1671
|
-
if (await rootIndexReferencesChildRoute(
|
|
1672
|
-
|
|
1890
|
+
if (await rootIndexReferencesChildRoute(
|
|
1891
|
+
rootDir,
|
|
1892
|
+
config.truthmark.paths.routesIndex,
|
|
1893
|
+
childRoutePath
|
|
1894
|
+
)) {
|
|
1895
|
+
results.push(
|
|
1896
|
+
await ensureRepoFile(
|
|
1897
|
+
rootDir,
|
|
1898
|
+
childRoutePath,
|
|
1899
|
+
renderChildAreaTemplate(config)
|
|
1900
|
+
)
|
|
1901
|
+
);
|
|
1673
1902
|
}
|
|
1674
1903
|
results.push(
|
|
1675
1904
|
await ensureRepoFile(
|
|
1676
1905
|
rootDir,
|
|
1677
|
-
`${
|
|
1678
|
-
renderTruthRootReadmeTemplate(config)
|
|
1906
|
+
`${resolveEngineeringTruthRoot(config)}/README.md`,
|
|
1907
|
+
renderTruthRootReadmeTemplate(config, "engineering")
|
|
1908
|
+
)
|
|
1909
|
+
);
|
|
1910
|
+
results.push(
|
|
1911
|
+
await ensureRepoFile(
|
|
1912
|
+
rootDir,
|
|
1913
|
+
`${config.truthmark.paths.productTruthRoot}/README.md`,
|
|
1914
|
+
renderTruthRootReadmeTemplate(config, "product")
|
|
1679
1915
|
)
|
|
1680
1916
|
);
|
|
1681
1917
|
results.push(
|
|
@@ -1688,45 +1924,52 @@ var scaffoldHierarchy = async (rootDir, config) => {
|
|
|
1688
1924
|
results.push(
|
|
1689
1925
|
await ensureOrUpdateTruthDocTemplate(
|
|
1690
1926
|
rootDir,
|
|
1691
|
-
truthTemplatePath(config,
|
|
1927
|
+
truthTemplatePath(config, BEHAVIOR_DOC_TEMPLATE_FILE_NAME),
|
|
1692
1928
|
renderBehaviorDocTemplateFile()
|
|
1693
1929
|
)
|
|
1694
1930
|
);
|
|
1695
1931
|
results.push(
|
|
1696
1932
|
await ensureOrUpdateTruthDocTemplate(
|
|
1697
1933
|
rootDir,
|
|
1698
|
-
truthTemplatePath(config,
|
|
1934
|
+
truthTemplatePath(config, CONTRACT_DOC_TEMPLATE_FILE_NAME),
|
|
1699
1935
|
renderContractDocTemplateFile()
|
|
1700
1936
|
)
|
|
1701
1937
|
);
|
|
1702
1938
|
results.push(
|
|
1703
1939
|
await ensureOrUpdateTruthDocTemplate(
|
|
1704
1940
|
rootDir,
|
|
1705
|
-
truthTemplatePath(config,
|
|
1941
|
+
truthTemplatePath(config, ARCHITECTURE_DOC_TEMPLATE_FILE_NAME),
|
|
1706
1942
|
renderArchitectureDocTemplateFile()
|
|
1707
1943
|
)
|
|
1708
1944
|
);
|
|
1709
1945
|
results.push(
|
|
1710
1946
|
await ensureOrUpdateTruthDocTemplate(
|
|
1711
1947
|
rootDir,
|
|
1712
|
-
truthTemplatePath(config,
|
|
1948
|
+
truthTemplatePath(config, WORKFLOW_DOC_TEMPLATE_FILE_NAME),
|
|
1713
1949
|
renderWorkflowDocTemplateFile()
|
|
1714
1950
|
)
|
|
1715
1951
|
);
|
|
1716
1952
|
results.push(
|
|
1717
1953
|
await ensureOrUpdateTruthDocTemplate(
|
|
1718
1954
|
rootDir,
|
|
1719
|
-
truthTemplatePath(config,
|
|
1955
|
+
truthTemplatePath(config, OPERATIONS_DOC_TEMPLATE_FILE_NAME),
|
|
1720
1956
|
renderOperationsDocTemplateFile()
|
|
1721
1957
|
)
|
|
1722
1958
|
);
|
|
1723
1959
|
results.push(
|
|
1724
1960
|
await ensureOrUpdateTruthDocTemplate(
|
|
1725
1961
|
rootDir,
|
|
1726
|
-
truthTemplatePath(config,
|
|
1962
|
+
truthTemplatePath(config, TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME),
|
|
1727
1963
|
renderTestBehaviorDocTemplateFile()
|
|
1728
1964
|
)
|
|
1729
1965
|
);
|
|
1966
|
+
results.push(
|
|
1967
|
+
await ensureOrUpdateTruthDocTemplate(
|
|
1968
|
+
rootDir,
|
|
1969
|
+
truthTemplatePath(config, PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME),
|
|
1970
|
+
renderProductCapabilityDocTemplateFile()
|
|
1971
|
+
)
|
|
1972
|
+
);
|
|
1730
1973
|
const behaviorDocTemplate = await readBehaviorDocTemplate(rootDir, config);
|
|
1731
1974
|
results.push(
|
|
1732
1975
|
await ensureRepoFile(
|
|
@@ -1766,11 +2009,51 @@ var renderAuditEvidenceCheckedSection = (items) => {
|
|
|
1766
2009
|
};
|
|
1767
2010
|
|
|
1768
2011
|
// src/agents/shared.ts
|
|
2012
|
+
var renderBulletLine = (line) => {
|
|
2013
|
+
const normalized = line.trim().replace(/^-\s*/u, "");
|
|
2014
|
+
return `- ${normalized}`;
|
|
2015
|
+
};
|
|
2016
|
+
var renderBulletBlock = (lines, indent = " ") => {
|
|
2017
|
+
return lines.split(/\n/u).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => `${indent}${renderBulletLine(line)}`).join("\n");
|
|
2018
|
+
};
|
|
2019
|
+
var renderLaneClassificationRuleBlock = (config = defaultAgentConfig(), indent = " ") => {
|
|
2020
|
+
const [, ...rules] = renderLaneClassificationInstructions(config).split(/\n/u);
|
|
2021
|
+
return renderBulletBlock(rules.join("\n"), indent);
|
|
2022
|
+
};
|
|
2023
|
+
var renderReadOnlyLaneClassificationRuleBlock = (config = defaultAgentConfig(), indent = " ") => {
|
|
2024
|
+
const productTruthRoot = resolveProductTruthRoot(config);
|
|
2025
|
+
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
2026
|
+
return renderBulletBlock(
|
|
2027
|
+
[
|
|
2028
|
+
"classify the request or changed surface as product-lane, engineering-lane, both-lane, or ambiguous for reporting only",
|
|
2029
|
+
`product-lane ownership belongs under ${productTruthRoot} and describes product promises, boundaries, rationale, decisions, and success criteria`,
|
|
2030
|
+
`engineering-lane ownership belongs under ${engineeringTruthRoot} and describes source-backed current realization, contracts, architecture, workflows, operations, or tests`,
|
|
2031
|
+
"both-lane ownership uses separate product and engineering docs cross-linked in route YAML with realized_by and realizes, not in doc frontmatter",
|
|
2032
|
+
"ambiguous lane ownership should be reported as blocked or routed to Truth Structure",
|
|
2033
|
+
LANE_INVARIANT
|
|
2034
|
+
].join("\n"),
|
|
2035
|
+
indent
|
|
2036
|
+
);
|
|
2037
|
+
};
|
|
1769
2038
|
var DECISION_TRUTH_INSTRUCTIONS = [
|
|
1770
2039
|
"Decision truth lives in the canonical doc it governs; date active decisions inline when added or changed.",
|
|
1771
2040
|
"Do not create separate active-decision ADR/planning logs; replace the active decision and let Git history carry the audit trail.",
|
|
1772
|
-
"
|
|
2041
|
+
"Product decisions belong in product truth; engineering, architecture, contract, workflow, and operational decisions belong in engineering truth."
|
|
1773
2042
|
].join("\n");
|
|
2043
|
+
var LANE_INVARIANT = "Do not make product docs a summary of engineering docs. Do not make engineering docs a detailed version of product docs. Product truth says what must be true and why. Engineering truth says how the repository currently realizes it.";
|
|
2044
|
+
var renderLaneClassificationInstructions = (config = defaultAgentConfig()) => {
|
|
2045
|
+
const productTruthRoot = resolveProductTruthRoot(config);
|
|
2046
|
+
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
2047
|
+
return [
|
|
2048
|
+
"Lane classification gate:",
|
|
2049
|
+
"- before writing canonical truth docs, classify the request or change as product-lane, engineering-lane, both-lane, or ambiguous",
|
|
2050
|
+
`- product-lane writes belong under ${productTruthRoot} and state product promises, boundaries, rationale, decisions, and success criteria`,
|
|
2051
|
+
`- engineering-lane writes belong under ${engineeringTruthRoot} and state source-backed current realization, contracts, architecture, workflows, operations, or tests`,
|
|
2052
|
+
"- both-lane work must write separate product and engineering docs and cross-link them in route YAML with realized_by and realizes, not in doc frontmatter",
|
|
2053
|
+
"- ambiguous lane ownership must block or invoke Truth Structure instead of writing a mixed document",
|
|
2054
|
+
`- ${LANE_INVARIANT}`
|
|
2055
|
+
].join("\n");
|
|
2056
|
+
};
|
|
1774
2057
|
var EVIDENCE_AUTHORITY_INSTRUCTIONS = [
|
|
1775
2058
|
"Repository instruction files and explicitly configured policy docs remain instruction authority when present; do not assume a repository uses any particular policy path.",
|
|
1776
2059
|
"Implementation code and canonical truth docs are inspected evidence for current behavior; they do not silently override workflow write boundaries."
|
|
@@ -1782,10 +2065,10 @@ var REPOSITORY_INTELLIGENCE_INSTRUCTIONS = [
|
|
|
1782
2065
|
].join("\n");
|
|
1783
2066
|
var FEATURE_DOC_TEMPLATE_INSTRUCTIONS = [
|
|
1784
2067
|
"When creating or updating a truth doc, inspect the routed truth kind and use the matching template under the configured Truthmark templates root.",
|
|
1785
|
-
"Supported kinds: behavior, contract, architecture, workflow, operations, and test-behavior.",
|
|
2068
|
+
"Supported kinds: product-capability, engineering-behavior, engineering-contract, engineering-architecture, engineering-workflow, engineering-operations, and engineering-test-behavior.",
|
|
1786
2069
|
"Treat the HTML comments under each template section as normative authoring guidance for that section.",
|
|
1787
2070
|
"Align existing docs to that template and write or repair section content so it satisfies the comment guidance while preserving accurate authored content.",
|
|
1788
|
-
"If the template is missing, use
|
|
2071
|
+
"If the template is missing, use lane-specific sections: product truth says what must be true and why; engineering truth says how the repository currently realizes it.",
|
|
1789
2072
|
"Teams may edit template files under the configured Truthmark templates root to define their local truth-doc standards."
|
|
1790
2073
|
].join("\n");
|
|
1791
2074
|
var renderTruthDocOwnershipGateSection = (subject, outcome) => {
|
|
@@ -1798,12 +2081,12 @@ var renderTruthDocOwnershipGateSection = (subject, outcome) => {
|
|
|
1798
2081
|
].join("\n");
|
|
1799
2082
|
};
|
|
1800
2083
|
var TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS = [
|
|
1801
|
-
"
|
|
1802
|
-
"- before any truth-doc split, restructure, or shape repair, inventory existing Product Decisions and Rationale sections in every source or touched truth doc",
|
|
1803
|
-
"- preserve each current decision and rationale in the
|
|
2084
|
+
"Decision/Rationale preservation gate:",
|
|
2085
|
+
"- before any truth-doc split, restructure, or shape repair, inventory existing Product Decisions, Engineering Decisions, and Rationale sections in every source or touched truth doc",
|
|
2086
|
+
"- preserve each current decision and rationale in the correct product or engineering lane owner; when splitting, move it to the new owner doc rather than deleting it or leaving it in an index",
|
|
1804
2087
|
"- remove or narrow a decision or rationale only when checkout evidence shows it is stale or unsupported, and report the exact claim, evidence, and result",
|
|
1805
2088
|
"- if ownership of a decision or rationale is unclear, block with manual-review files instead of deleting it or guessing",
|
|
1806
|
-
"- after the edit, verify every touched truth doc
|
|
2089
|
+
"- after the edit, verify every touched truth doc keeps lane-appropriate decision/rationale sections and every pre-existing entry is preserved, moved, narrowed, removed with evidence, or blocked"
|
|
1807
2090
|
].join("\n");
|
|
1808
2091
|
var renderTruthDocRestructureGateSection = (scope) => {
|
|
1809
2092
|
return [
|
|
@@ -1824,7 +2107,7 @@ var renderRouteFirstEvidenceGateSection = (subject, noImpactedDocOutcome) => {
|
|
|
1824
2107
|
return [
|
|
1825
2108
|
"Evidence Gate:",
|
|
1826
2109
|
`- route-first: map ${subject} to bounded route owners and primary canonical docs`,
|
|
1827
|
-
"- review new or changed behavior-bearing claims only in touched docs, route ownership,
|
|
2110
|
+
"- review new or changed behavior-bearing claims only in touched docs, route ownership, lane-specific decisions, and rationale",
|
|
1828
2111
|
"- support claims with primary checkout evidence: implementation, config, routing, generated templates, schemas, or contract definitions",
|
|
1829
2112
|
"- tests/examples/canonical docs corroborate; they are not sole proof when implementation conflicts",
|
|
1830
2113
|
"- remove, narrow, or block unsupported claims",
|
|
@@ -1834,7 +2117,7 @@ var renderRouteFirstEvidenceGateSection = (subject, noImpactedDocOutcome) => {
|
|
|
1834
2117
|
var renderTopologyEvidenceGateSection = () => {
|
|
1835
2118
|
return [
|
|
1836
2119
|
"Evidence Gate:",
|
|
1837
|
-
"- apply the Evidence Gate before finishing when Truth Structure writes routed docs, ownership claims,
|
|
2120
|
+
"- apply the Evidence Gate before finishing when Truth Structure writes routed docs, ownership claims, lane-specific decisions, or rationale",
|
|
1838
2121
|
"- support ownership/behavior claims with topology or primary checkout evidence from layout, implementation boundaries, docs, config, route files, tests, templates, schemas, or contracts",
|
|
1839
2122
|
"- tests/examples/canonical docs corroborate; remove, narrow, or block unsupported claims"
|
|
1840
2123
|
].join("\n");
|
|
@@ -1868,7 +2151,9 @@ var renderCodexSubagentModeSection = (agents, parentRule, writeAgents = []) => {
|
|
|
1868
2151
|
};
|
|
1869
2152
|
var renderOpenCodeSubagentModeSection = (agents, parentRule, writeAgents = []) => {
|
|
1870
2153
|
const mentions = agents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
|
|
1871
|
-
const writeMentions = writeAgents.map(
|
|
2154
|
+
const writeMentions = writeAgents.map(
|
|
2155
|
+
(agent) => `@${agent.replace(/_/gu, "-")}`
|
|
2156
|
+
);
|
|
1872
2157
|
const writeAgentLines = writeMentions.length > 0 ? [
|
|
1873
2158
|
`- dispatch write-capable project subagents only with explicit write leases: ${writeMentions.join(", ")}`,
|
|
1874
2159
|
"- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
|
|
@@ -1888,7 +2173,9 @@ var renderOpenCodeSubagentModeSection = (agents, parentRule, writeAgents = []) =
|
|
|
1888
2173
|
].join("\n");
|
|
1889
2174
|
};
|
|
1890
2175
|
var renderClaudeSubagentModeSection = (agents, parentRule, writeAgents = []) => {
|
|
1891
|
-
const mentions = agents.map(
|
|
2176
|
+
const mentions = agents.map(
|
|
2177
|
+
(agent) => `${agent.replace(/_/gu, "-")} subagent`
|
|
2178
|
+
);
|
|
1892
2179
|
const writeMentions = writeAgents.map(
|
|
1893
2180
|
(agent) => `${agent.replace(/_/gu, "-")} subagent`
|
|
1894
2181
|
);
|
|
@@ -1912,7 +2199,9 @@ var renderClaudeSubagentModeSection = (agents, parentRule, writeAgents = []) =>
|
|
|
1912
2199
|
};
|
|
1913
2200
|
var renderCopilotCustomAgentModeSection = (agents, parentRule, writeAgents = []) => {
|
|
1914
2201
|
const mentions = agents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
|
|
1915
|
-
const writeMentions = writeAgents.map(
|
|
2202
|
+
const writeMentions = writeAgents.map(
|
|
2203
|
+
(agent) => `@${agent.replace(/_/gu, "-")}`
|
|
2204
|
+
);
|
|
1916
2205
|
const writeAgentLines = writeMentions.length > 0 ? [
|
|
1917
2206
|
`- dispatch write-capable project custom agents only with explicit write leases: ${writeMentions.join(", ")}`,
|
|
1918
2207
|
"- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
|
|
@@ -1933,7 +2222,9 @@ var renderCopilotCustomAgentModeSection = (agents, parentRule, writeAgents = [])
|
|
|
1933
2222
|
};
|
|
1934
2223
|
var renderGeminiSubagentModeSection = (agents, parentRule, writeAgents = []) => {
|
|
1935
2224
|
const mentions = agents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
|
|
1936
|
-
const writeMentions = writeAgents.map(
|
|
2225
|
+
const writeMentions = writeAgents.map(
|
|
2226
|
+
(agent) => `@${agent.replace(/_/gu, "-")}`
|
|
2227
|
+
);
|
|
1937
2228
|
const writeAgentLines = writeMentions.length > 0 ? [
|
|
1938
2229
|
`- dispatch write-capable project subagents only with explicit write leases: ${writeMentions.join(", ")}`,
|
|
1939
2230
|
"- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
|
|
@@ -1956,13 +2247,15 @@ var defaultAgentConfig = () => {
|
|
|
1956
2247
|
return createDefaultConfig();
|
|
1957
2248
|
};
|
|
1958
2249
|
var renderHierarchySummary = (config) => {
|
|
1959
|
-
const
|
|
2250
|
+
const productRoot = resolveProductTruthRoot(config);
|
|
2251
|
+
const engineeringRoot = resolveEngineeringTruthRoot(config);
|
|
1960
2252
|
return [
|
|
1961
2253
|
"Truthmark hierarchy hints:",
|
|
1962
2254
|
"- Config, when present: .truthmark/config.yml",
|
|
1963
2255
|
`- Root route index, when present: ${config.truthmark.paths.routesIndex}`,
|
|
1964
2256
|
`- Area route files, when present: ${config.truthmark.paths.routeAreasRoot}/**/*.md`,
|
|
1965
|
-
`-
|
|
2257
|
+
`- Product truth docs, when present: ${productRoot}/**/*.md`,
|
|
2258
|
+
`- Engineering truth docs, when present: ${engineeringRoot}/**/*.md`
|
|
1966
2259
|
].join("\n");
|
|
1967
2260
|
};
|
|
1968
2261
|
|
|
@@ -1977,8 +2270,12 @@ var TRUTHMARK_VERSION = packageJson.version;
|
|
|
1977
2270
|
var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
|
|
1978
2271
|
var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
|
|
1979
2272
|
var renderCompactHierarchySummary = (config) => {
|
|
1980
|
-
const
|
|
1981
|
-
|
|
2273
|
+
const productTruthRoot = resolveProductTruthRoot(config);
|
|
2274
|
+
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
2275
|
+
const truthDocRoots = Array.from(/* @__PURE__ */ new Set([productTruthRoot, engineeringTruthRoot])).map(
|
|
2276
|
+
(truthRoot3) => `${truthRoot3}/**/*.md`
|
|
2277
|
+
);
|
|
2278
|
+
return `Hierarchy hints: config .truthmark/config.yml when present; routes ${config.truthmark.paths.routesIndex} and ${config.truthmark.paths.routeAreasRoot}/**/*.md when present; Truth docs: ${truthDocRoots.join(" and ")} when present.`;
|
|
1982
2279
|
};
|
|
1983
2280
|
var renderAgentsBlock = (config = defaultAgentConfig()) => {
|
|
1984
2281
|
const portalLine = config.truthmark.generated.portal.enabled ? `Truthmark Portal is a separate manual-only presentation workflow. Run it only when explicitly requested; it writes generated non-canonical static files under ${config.truthmark.paths.portalOutput}/. Markdown remains canonical.` : null;
|
|
@@ -2071,7 +2368,8 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
|
|
|
2071
2368
|
requiredGates: [
|
|
2072
2369
|
"topology quality",
|
|
2073
2370
|
"truth-doc ownership",
|
|
2074
|
-
"
|
|
2371
|
+
"lane classification",
|
|
2372
|
+
"Decision/Rationale preservation",
|
|
2075
2373
|
"truth-doc shape repair when restructuring",
|
|
2076
2374
|
"Evidence Gate"
|
|
2077
2375
|
],
|
|
@@ -2114,7 +2412,9 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
|
|
|
2114
2412
|
],
|
|
2115
2413
|
requiredGates: [
|
|
2116
2414
|
"truth-doc ownership",
|
|
2117
|
-
"
|
|
2415
|
+
"lane classification",
|
|
2416
|
+
"Decision/Rationale preservation",
|
|
2417
|
+
"lane split and relationship repair",
|
|
2118
2418
|
"truth-doc shape repair when restructuring",
|
|
2119
2419
|
"Evidence Gate"
|
|
2120
2420
|
],
|
|
@@ -2155,7 +2455,8 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
|
|
|
2155
2455
|
],
|
|
2156
2456
|
requiredGates: [
|
|
2157
2457
|
"truth-doc ownership",
|
|
2158
|
-
"
|
|
2458
|
+
"lane classification",
|
|
2459
|
+
"Decision/Rationale preservation",
|
|
2159
2460
|
"Evidence Gate",
|
|
2160
2461
|
"truth-doc shape repair when restructuring"
|
|
2161
2462
|
],
|
|
@@ -2191,10 +2492,10 @@ var TRUTHMARK_WORKFLOW_MANIFEST = {
|
|
|
2191
2492
|
"truth audit"
|
|
2192
2493
|
],
|
|
2193
2494
|
forbiddenAdjacency: [
|
|
2194
|
-
"must not edit truth docs",
|
|
2195
|
-
"must not edit truth routing"
|
|
2495
|
+
"must not edit truth docs except through follow-up Truth Sync",
|
|
2496
|
+
"must not edit truth routing except through follow-up Truth Sync"
|
|
2196
2497
|
],
|
|
2197
|
-
requiredGates: ["truth-doc ownership"],
|
|
2498
|
+
requiredGates: ["lane classification", "truth-doc ownership"],
|
|
2198
2499
|
allowedWrites: ["functional code"],
|
|
2199
2500
|
reportSections: ["Truth docs used", "Code updated", "Verification"]
|
|
2200
2501
|
},
|
|
@@ -2326,9 +2627,6 @@ var getTruthmarkWorkflow = (id) => {
|
|
|
2326
2627
|
};
|
|
2327
2628
|
|
|
2328
2629
|
// src/agents/truth-check.ts
|
|
2329
|
-
var renderMarkdownExample = (content) => {
|
|
2330
|
-
return ["```md", content, "```"].join("\n");
|
|
2331
|
-
};
|
|
2332
2630
|
var TRUTH_CHECK_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-check; Codex /truthmark-check or $truthmark-check; Claude Code /truthmark-check; GitHub Copilot /truthmark-check; Gemini CLI /truthmark:check.";
|
|
2333
2631
|
var renderTruthCheckReportExample = (config = defaultAgentConfig()) => {
|
|
2334
2632
|
const rootRouteIndex = config.truthmark.paths.routesIndex;
|
|
@@ -2355,7 +2653,7 @@ ${renderAuditEvidenceCheckedSection([
|
|
|
2355
2653
|
Validation:
|
|
2356
2654
|
- truthmark check`;
|
|
2357
2655
|
};
|
|
2358
|
-
var
|
|
2656
|
+
var renderTruthCheckProcedureBody = (config = defaultAgentConfig(), options = {}) => {
|
|
2359
2657
|
const workflow = getTruthmarkWorkflow("truthmark-check");
|
|
2360
2658
|
const claudeSubagentMode = options.includeClaudeSubagentMode ? `${renderClaudeSubagentModeSection(
|
|
2361
2659
|
workflow.subagents ?? [],
|
|
@@ -2382,15 +2680,7 @@ var renderTruthCheckSkillBody = (config = defaultAgentConfig(), options = {}) =>
|
|
|
2382
2680
|
|
|
2383
2681
|
` : "";
|
|
2384
2682
|
const subagentMode = `${claudeSubagentMode}${codexSubagentMode}${copilotCustomAgentMode}${openCodeSubagentMode}`;
|
|
2385
|
-
return
|
|
2386
|
-
name: truthmark-check
|
|
2387
|
-
description: ${workflow.description}
|
|
2388
|
-
argument-hint: Optional area, doc path, or audit focus
|
|
2389
|
-
user-invocable: true
|
|
2390
|
-
truthmark-version: ${TRUTHMARK_VERSION}
|
|
2391
|
-
---
|
|
2392
|
-
|
|
2393
|
-
# Truthmark Check
|
|
2683
|
+
return `# Truthmark Check
|
|
2394
2684
|
|
|
2395
2685
|
Use this skill to audit repository truth health.
|
|
2396
2686
|
|
|
@@ -2399,12 +2689,25 @@ Invocations: ${TRUTH_CHECK_EXPLICIT_INVOCATIONS}
|
|
|
2399
2689
|
Truth Check is agent-led:
|
|
2400
2690
|
|
|
2401
2691
|
- inspect .truthmark/config.yml and configured route files only when they exist; then inspect canonical docs and relevant implementation directly
|
|
2402
|
-
- ${EVIDENCE_AUTHORITY_INSTRUCTIONS}
|
|
2403
2692
|
- inspect the configured root route index at ${config.truthmark.paths.routesIndex} and relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ when they exist
|
|
2693
|
+
- Evidence authority:
|
|
2694
|
+
${renderBulletBlock(EVIDENCE_AUTHORITY_INSTRUCTIONS)}
|
|
2695
|
+
- Lane classification:
|
|
2696
|
+
${renderReadOnlyLaneClassificationRuleBlock(config)}
|
|
2404
2697
|
- check that current docs describe current code rather than historical plans
|
|
2698
|
+
- keep lane and cross-lane checks route-first and bounded:
|
|
2699
|
+
- for a narrow audit, inspect only the routed area and directly linked counterpart docs
|
|
2700
|
+
- for root-wide truth health, first build a cheap route-map/index from route files, then inspect only mismatches and linked leaves
|
|
2701
|
+
- inspect product counterparts for engineering docs only when route YAML claims a product relationship, or when the user explicitly asks for user-visible product coverage
|
|
2702
|
+
- check lane root/kind alignment for product truth under ${config.truthmark.paths.productTruthRoot} and engineering truth under ${config.truthmark.paths.engineeringTruthRoot}
|
|
2703
|
+
- check route YAML cross-lane realized_by and realizes links for existence and lane compatibility
|
|
2704
|
+
- report missing product links for user-visible engineering docs only as a second-pass review diagnostic, not as default full-document reads or hard errors
|
|
2705
|
+
- check product docs do not contain engineering execution flow, generated file inventories, or CLI envelope mechanics
|
|
2706
|
+
- check engineering docs do not contain product promises, product rationale, or Product Decisions sections
|
|
2707
|
+
- never judge whether a product decision is commercially correct, valuable, prioritized, or desirable
|
|
2405
2708
|
- check that route files map code surfaces to canonical truth docs when route files exist
|
|
2406
2709
|
- check for broad, catch-all, index-like, or mixed-owner truth docs and report them as topology issues requiring Truth Structure
|
|
2407
|
-
- check that canonical
|
|
2710
|
+
- check that canonical docs keep lane-appropriate decisions and rationale sections
|
|
2408
2711
|
- optionally run truthmark check when local tooling is available
|
|
2409
2712
|
- must not require the truthmark binary; direct inspection is always valid
|
|
2410
2713
|
- report issues and suggested fixes without silently rewriting unrelated files
|
|
@@ -2412,20 +2715,13 @@ Truth Check is agent-led:
|
|
|
2412
2715
|
${renderAuditEvidenceGateSection()}
|
|
2413
2716
|
|
|
2414
2717
|
${subagentMode}${renderHierarchySummary(config)}
|
|
2415
|
-
${DECISION_TRUTH_INSTRUCTIONS}
|
|
2416
|
-
|
|
2417
|
-
Report completion in this shape:
|
|
2418
|
-
|
|
2419
|
-
${renderMarkdownExample(renderTruthCheckReportExample(config))}`;
|
|
2718
|
+
${DECISION_TRUTH_INSTRUCTIONS}`;
|
|
2420
2719
|
};
|
|
2421
2720
|
|
|
2422
2721
|
// src/agents/truth-document.ts
|
|
2423
|
-
var renderMarkdownExample2 = (content) => {
|
|
2424
|
-
return ["```md", content, "```"].join("\n");
|
|
2425
|
-
};
|
|
2426
2722
|
var TRUTH_DOCUMENT_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-document; Codex /truthmark-document or $truthmark-document; Claude Code /truthmark-document; GitHub Copilot /truthmark-document; Gemini CLI /truthmark:document.";
|
|
2427
2723
|
var renderTruthDocumentReportExample = (config = defaultAgentConfig()) => {
|
|
2428
|
-
const
|
|
2724
|
+
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
2429
2725
|
const helperScripts = ["validate-write-lease: skipped, no write lease used"];
|
|
2430
2726
|
return `Truth Document: completed
|
|
2431
2727
|
|
|
@@ -2436,13 +2732,13 @@ Ownership reviewed:
|
|
|
2436
2732
|
- ${config.truthmark.paths.routesIndex}
|
|
2437
2733
|
|
|
2438
2734
|
Truth docs created:
|
|
2439
|
-
- ${
|
|
2735
|
+
- ${engineeringTruthRoot}/contracts/routing.md
|
|
2440
2736
|
|
|
2441
2737
|
Truth docs updated:
|
|
2442
|
-
- ${
|
|
2738
|
+
- ${engineeringTruthRoot}/behaviors/check-diagnostics.md
|
|
2443
2739
|
|
|
2444
2740
|
Truth docs restructured:
|
|
2445
|
-
- ${
|
|
2741
|
+
- ${engineeringTruthRoot}/behaviors/check-diagnostics.md
|
|
2446
2742
|
|
|
2447
2743
|
Routing updated:
|
|
2448
2744
|
- ${config.truthmark.paths.routesIndex}
|
|
@@ -2464,7 +2760,7 @@ ${helperScripts.map((helperScript) => `- ${helperScript}`).join("\n")}
|
|
|
2464
2760
|
Notes:
|
|
2465
2761
|
- Documented routing and behavior from route handlers and tests.`;
|
|
2466
2762
|
};
|
|
2467
|
-
var
|
|
2763
|
+
var renderTruthDocumentProcedureBody = (config = defaultAgentConfig(), options = {}) => {
|
|
2468
2764
|
const workflow = getTruthmarkWorkflow("truthmark-document");
|
|
2469
2765
|
const claudeSubagentMode = options.includeClaudeSubagentMode ? `${renderClaudeSubagentModeSection(
|
|
2470
2766
|
workflow.subagents ?? [],
|
|
@@ -2491,15 +2787,7 @@ var renderTruthDocumentSkillBody = (config = defaultAgentConfig(), options = {})
|
|
|
2491
2787
|
)}
|
|
2492
2788
|
` : "";
|
|
2493
2789
|
const subagentMode = `${claudeSubagentMode}${codexSubagentMode}${copilotCustomAgentMode}${openCodeSubagentMode}`;
|
|
2494
|
-
return
|
|
2495
|
-
name: truthmark-document
|
|
2496
|
-
description: ${workflow.description}
|
|
2497
|
-
argument-hint: Optional implemented behavior, API endpoint, route, controller, package, or truth-doc area to document
|
|
2498
|
-
user-invocable: true
|
|
2499
|
-
truthmark-version: ${TRUTHMARK_VERSION}
|
|
2500
|
-
---
|
|
2501
|
-
|
|
2502
|
-
# Truthmark Document
|
|
2790
|
+
return `# Truthmark Document
|
|
2503
2791
|
|
|
2504
2792
|
Use this skill to document existing implemented behavior when no functional-code changes are required for the task.
|
|
2505
2793
|
Invocations: ${TRUTH_DOCUMENT_EXPLICIT_INVOCATIONS}
|
|
@@ -2508,7 +2796,10 @@ Truth Document is manual and implementation-first:
|
|
|
2508
2796
|
|
|
2509
2797
|
- run only when the user explicitly asks to generate or update truth docs for existing behavior, or when Truth Sync, Truth Check, or Truth Structure reports implemented behavior that lacks canonical truth docs
|
|
2510
2798
|
- inspect .truthmark/config.yml and configured route files only when they exist; then inspect existing canonical docs, implementation code, and tests directly
|
|
2511
|
-
-
|
|
2799
|
+
- Evidence authority:
|
|
2800
|
+
${renderBulletBlock(EVIDENCE_AUTHORITY_INSTRUCTIONS)}
|
|
2801
|
+
- Lane classification:
|
|
2802
|
+
${renderLaneClassificationRuleBlock(config)}
|
|
2512
2803
|
- document current implemented behavior; do not invent future behavior or planned endpoints
|
|
2513
2804
|
- may write canonical truth docs and ${config.truthmark.paths.routesIndex} or relevant child route files only
|
|
2514
2805
|
- must not write functional code
|
|
@@ -2516,7 +2807,10 @@ Truth Document is manual and implementation-first:
|
|
|
2516
2807
|
- block and recommend Truth Structure when routing repair is unsafe, ambiguous, or outside the task boundary
|
|
2517
2808
|
- keep feature README.md files as indexes rather than truth-document targets
|
|
2518
2809
|
- create or update bounded leaf truth docs when behavior does not fit an existing leaf doc
|
|
2519
|
-
-
|
|
2810
|
+
- write product capability/boundary truth under ${config.truthmark.paths.productTruthRoot} when documenting product promise, boundary, rationale, or user/stakeholder value
|
|
2811
|
+
- write engineering truth under ${config.truthmark.paths.engineeringTruthRoot} when documenting implementation behavior, contracts, architecture, workflows, operations, or tests
|
|
2812
|
+
- for both-lane documentation requests, write separate product and engineering docs and cross-link them in route YAML with realized_by and realizes, not in doc frontmatter
|
|
2813
|
+
- keep engineering behavior truth behavior-oriented, not endpoint-oriented, unless the endpoint itself is the behavior boundary
|
|
2520
2814
|
- keep API endpoint details in the nearest contract truth doc when such a doc owns the API contract
|
|
2521
2815
|
- preserve unrelated authored content
|
|
2522
2816
|
${renderTruthDocOwnershipGateSection(
|
|
@@ -2546,19 +2840,12 @@ Parent post-document verification:
|
|
|
2546
2840
|
- verify only truth docs and leased truth routing files changed during document work
|
|
2547
2841
|
- block on functional code, generated host surfaces, or unrelated diffs caused by document work
|
|
2548
2842
|
- for each write lease, validate the worker report against the actual worker diff, allowedWrites, forbiddenWrites, identity fields, filesChanged, offLeaseChanges, blockers, and required report fields before accepting it
|
|
2549
|
-
- verify the final report records ownership review, structure requirement, restructure, routing update, or blocked reason when applicable
|
|
2550
|
-
|
|
2551
|
-
Report completion in this shape:
|
|
2552
|
-
${renderMarkdownExample2(renderTruthDocumentReportExample(config))}`;
|
|
2843
|
+
- verify the final report records ownership review, structure requirement, restructure, routing update, or blocked reason when applicable`;
|
|
2553
2844
|
};
|
|
2554
2845
|
|
|
2555
2846
|
// src/agents/truth-preview.ts
|
|
2556
|
-
var renderMarkdownExample3 = (content) => {
|
|
2557
|
-
return ["```md", content, "```"].join("\n");
|
|
2558
|
-
};
|
|
2559
2847
|
var TRUTH_PREVIEW_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-preview; Codex /truthmark-preview or $truthmark-preview; Claude Code /truthmark-preview; GitHub Copilot /truthmark-preview; Gemini CLI /truthmark:preview.";
|
|
2560
2848
|
var renderTruthPreviewReportExample = (config = defaultAgentConfig()) => {
|
|
2561
|
-
const truthDocsRoot = resolveTruthDocsRoot(config);
|
|
2562
2849
|
return `Truth Preview: completed
|
|
2563
2850
|
|
|
2564
2851
|
Requested outcome:
|
|
@@ -2574,14 +2861,16 @@ Why this workflow:
|
|
|
2574
2861
|
|
|
2575
2862
|
Likely route owner:
|
|
2576
2863
|
- route file: ${config.truthmark.paths.routesIndex}
|
|
2577
|
-
-
|
|
2864
|
+
- likely lane impact: engineering-lane
|
|
2865
|
+
- product target docs: none identified
|
|
2866
|
+
- engineering target docs: ${config.truthmark.paths.engineeringTruthRoot}/behaviors/example.md
|
|
2578
2867
|
- confidence: medium
|
|
2579
2868
|
|
|
2580
2869
|
Expected write classes:
|
|
2581
2870
|
- truth docs
|
|
2582
2871
|
|
|
2583
2872
|
Expected target files:
|
|
2584
|
-
- ${
|
|
2873
|
+
- ${config.truthmark.paths.engineeringTruthRoot}/behaviors/example.md
|
|
2585
2874
|
|
|
2586
2875
|
Suggested subagent use:
|
|
2587
2876
|
- read-only verifiers: truth_route_auditor
|
|
@@ -2594,17 +2883,8 @@ Blocking ambiguity:
|
|
|
2594
2883
|
Handoff:
|
|
2595
2884
|
- Run the selected Truthmark workflow after user approval.`;
|
|
2596
2885
|
};
|
|
2597
|
-
var
|
|
2598
|
-
|
|
2599
|
-
return `---
|
|
2600
|
-
name: truthmark-preview
|
|
2601
|
-
description: ${workflow.description}
|
|
2602
|
-
argument-hint: Optional requested outcome, code area, doc path, or routing question
|
|
2603
|
-
user-invocable: true
|
|
2604
|
-
truthmark-version: ${TRUTHMARK_VERSION}
|
|
2605
|
-
---
|
|
2606
|
-
|
|
2607
|
-
Use this skill only when the user explicitly asks to preview Truthmark routing or workflow choice before edits.
|
|
2886
|
+
var renderTruthPreviewProcedureBody = (config = defaultAgentConfig()) => {
|
|
2887
|
+
return `Use this skill only when the user explicitly asks to preview Truthmark routing or workflow choice before edits.
|
|
2608
2888
|
|
|
2609
2889
|
Invocations: ${TRUTH_PREVIEW_EXPLICIT_INVOCATIONS}
|
|
2610
2890
|
|
|
@@ -2612,15 +2892,19 @@ Truth Preview is read-only. Its report is intended, not authorized.
|
|
|
2612
2892
|
|
|
2613
2893
|
Purpose:
|
|
2614
2894
|
- preview the likely Truthmark workflow, route owner, target files, expected write classes, suggested subagent use, and blocking ambiguity before edits happen
|
|
2895
|
+
- report likely product lane impact, engineering lane impact, target docs, and ambiguity before edits
|
|
2615
2896
|
- hand off to the selected workflow after user approval
|
|
2616
2897
|
- keep the selector thin so agents can avoid loading or acting through heavier workflows prematurely
|
|
2617
2898
|
|
|
2618
2899
|
Read:
|
|
2619
2900
|
- .truthmark/config.yml, only when present
|
|
2620
|
-
- ${config.truthmark.paths.routesIndex}, only when present
|
|
2621
|
-
- relevant child route files under ${config.truthmark.paths.routeAreasRoot}
|
|
2901
|
+
- ${config.truthmark.paths.routesIndex}, first, only when present
|
|
2902
|
+
- relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ for the selected scope or changed paths, only when present
|
|
2622
2903
|
- relevant truth docs and implementation files needed to preview ownership
|
|
2623
|
-
-
|
|
2904
|
+
- Evidence authority:
|
|
2905
|
+
${renderBulletBlock(EVIDENCE_AUTHORITY_INSTRUCTIONS)}
|
|
2906
|
+
- Lane classification:
|
|
2907
|
+
${renderReadOnlyLaneClassificationRuleBlock(config)}
|
|
2624
2908
|
|
|
2625
2909
|
Do not:
|
|
2626
2910
|
- must not edit files
|
|
@@ -2637,27 +2921,15 @@ Suggested subagent use:
|
|
|
2637
2921
|
- write workers: none
|
|
2638
2922
|
- leases needed: none
|
|
2639
2923
|
|
|
2640
|
-
${renderHierarchySummary(config)}
|
|
2641
|
-
|
|
2642
|
-
Report completion in this shape:
|
|
2643
|
-
${renderMarkdownExample3(renderTruthPreviewReportExample(config))}`;
|
|
2924
|
+
${renderHierarchySummary(config)}`;
|
|
2644
2925
|
};
|
|
2645
2926
|
|
|
2646
2927
|
// src/agents/truthmark-portal.ts
|
|
2647
2928
|
var TRUTHMARK_PORTAL_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-portal; Codex /truthmark-portal or $truthmark-portal; Claude Code /truthmark-portal; GitHub Copilot /truthmark-portal; Gemini CLI /truthmark:portal.";
|
|
2648
|
-
var
|
|
2649
|
-
const workflow = getTruthmarkWorkflow("truthmark-portal");
|
|
2929
|
+
var renderTruthmarkPortalProcedureBody = (config = defaultAgentConfig()) => {
|
|
2650
2930
|
const output = config.truthmark.paths.portalOutput;
|
|
2651
2931
|
const template = config.truthmark.paths.portalTemplate;
|
|
2652
|
-
return
|
|
2653
|
-
name: truthmark-portal
|
|
2654
|
-
description: ${workflow.description}
|
|
2655
|
-
argument-hint: Optional portal generation focus
|
|
2656
|
-
user-invocable: true
|
|
2657
|
-
truthmark-version: ${TRUTHMARK_VERSION}
|
|
2658
|
-
---
|
|
2659
|
-
|
|
2660
|
-
# Truthmark Portal
|
|
2932
|
+
return `# Truthmark Portal
|
|
2661
2933
|
|
|
2662
2934
|
Truthmark Portal is a manual-only presentation workflow. It is never a completion gate, never Truth Sync, and runs only when the user explicitly asks to generate, refresh, or update the committed static HTML Portal.
|
|
2663
2935
|
|
|
@@ -2688,48 +2960,19 @@ Workflow:
|
|
|
2688
2960
|
5. Replace or write only under ${output}; do not edit canonical Markdown, routing, source code, or instruction files unless the user explicitly changes scope.
|
|
2689
2961
|
6. Generate the multi-page static site with local assets/search metadata and visible source provenance.
|
|
2690
2962
|
7. Validate entry page, links where practical, provenance/disclaimers, local-only assets, and that metadata remains under ${output}/assets.
|
|
2691
|
-
${renderHierarchySummary(config)}
|
|
2692
|
-
|
|
2693
|
-
Report completion in this shape:
|
|
2694
|
-
|
|
2695
|
-
\`\`\`md
|
|
2696
|
-
Truthmark Portal: completed
|
|
2697
|
-
|
|
2698
|
-
Output path:
|
|
2699
|
-
- ${output}
|
|
2700
|
-
|
|
2701
|
-
Page count:
|
|
2702
|
-
- <count>
|
|
2703
|
-
|
|
2704
|
-
Diagrams/assets:
|
|
2705
|
-
- <generated diagrams/assets or none>
|
|
2706
|
-
|
|
2707
|
-
Source docs reviewed:
|
|
2708
|
-
- <source markdown paths>
|
|
2709
|
-
|
|
2710
|
-
Skipped/ambiguous docs:
|
|
2711
|
-
- <paths and reason, or none>
|
|
2712
|
-
|
|
2713
|
-
Validation:
|
|
2714
|
-
- <checks performed>
|
|
2715
|
-
|
|
2716
|
-
Markdown canonical statement:
|
|
2717
|
-
- Markdown remains canonical; generated Portal HTML is non-canonical presentation only.
|
|
2718
|
-
\`\`\`
|
|
2719
|
-
`;
|
|
2963
|
+
${renderHierarchySummary(config)}`;
|
|
2720
2964
|
};
|
|
2721
2965
|
|
|
2722
2966
|
// src/agents/truth-structure.ts
|
|
2723
|
-
var renderMarkdownExample4 = (content) => {
|
|
2724
|
-
return ["```md", content, "```"].join("\n");
|
|
2725
|
-
};
|
|
2726
2967
|
var TRUTH_STRUCTURE_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-structure; Codex /truthmark-structure or $truthmark-structure; Claude Code /truthmark-structure; GitHub Copilot /truthmark-structure; Gemini CLI /truthmark:structure.";
|
|
2727
2968
|
var renderTruthStructureReportExample = (config = defaultAgentConfig()) => {
|
|
2728
|
-
const
|
|
2969
|
+
const productTruthRoot = resolveProductTruthRoot(config);
|
|
2970
|
+
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
2729
2971
|
return `Truth Structure: completed
|
|
2730
2972
|
Topology reviewed:
|
|
2731
2973
|
- controllers: src/auth/**
|
|
2732
|
-
- docs root: ${
|
|
2974
|
+
- product docs root: ${productTruthRoot}
|
|
2975
|
+
- engineering docs root: ${engineeringTruthRoot}
|
|
2733
2976
|
- route files: ${config.truthmark.paths.routesIndex}
|
|
2734
2977
|
Areas reviewed:
|
|
2735
2978
|
- src/auth/**
|
|
@@ -2738,14 +2981,16 @@ Routing updated:
|
|
|
2738
2981
|
Initial truth boundary:
|
|
2739
2982
|
- Area: Authentication
|
|
2740
2983
|
- Code: src/auth/**
|
|
2741
|
-
-
|
|
2984
|
+
- Product owner: ${productTruthRoot}/capabilities/authentication-session.md
|
|
2985
|
+
- Engineering owner: ${engineeringTruthRoot}/behaviors/authentication-session.md
|
|
2742
2986
|
- Scope: session behavior only
|
|
2743
2987
|
Truth docs created:
|
|
2744
|
-
- ${
|
|
2988
|
+
- ${productTruthRoot}/capabilities/authentication-session.md
|
|
2989
|
+
- ${engineeringTruthRoot}/behaviors/authentication-session.md
|
|
2745
2990
|
Truth docs split:
|
|
2746
|
-
-
|
|
2991
|
+
- docs/truthmark/truth/authentication/README.md -> ${productTruthRoot}/capabilities/authentication-session.md and ${engineeringTruthRoot}/behaviors/authentication-session.md
|
|
2747
2992
|
Truth docs restructured:
|
|
2748
|
-
-
|
|
2993
|
+
- docs/truthmark/truth/authentication/README.md
|
|
2749
2994
|
${renderClaimEvidenceCheckedSection([
|
|
2750
2995
|
{
|
|
2751
2996
|
claim: "Session behavior belongs to a dedicated Authentication truth owner.",
|
|
@@ -2758,8 +3003,9 @@ Topology decisions:
|
|
|
2758
3003
|
Notes:
|
|
2759
3004
|
- Added an Authentication area for session behavior.`;
|
|
2760
3005
|
};
|
|
2761
|
-
var
|
|
2762
|
-
const
|
|
3006
|
+
var renderTruthStructureProcedureBody = (config = defaultAgentConfig(), options = {}) => {
|
|
3007
|
+
const productTruthRoot = resolveProductTruthRoot(config);
|
|
3008
|
+
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
2763
3009
|
const workflow = getTruthmarkWorkflow("truthmark-structure");
|
|
2764
3010
|
const claudeSubagentMode = options.includeClaudeSubagentMode ? `${renderClaudeSubagentModeSection(
|
|
2765
3011
|
workflow.subagents ?? [],
|
|
@@ -2772,30 +3018,26 @@ var renderTruthStructureSkillBody = (config = defaultAgentConfig(), options = {}
|
|
|
2772
3018
|
)}
|
|
2773
3019
|
` : "";
|
|
2774
3020
|
const subagentMode = `${claudeSubagentMode}${copilotCustomAgentMode}`;
|
|
2775
|
-
return
|
|
2776
|
-
name: truthmark-structure
|
|
2777
|
-
description: ${workflow.description}
|
|
2778
|
-
argument-hint: Optional area, directory, or routing concern
|
|
2779
|
-
user-invocable: true
|
|
2780
|
-
truthmark-version: ${TRUTHMARK_VERSION}
|
|
2781
|
-
---
|
|
2782
|
-
|
|
2783
|
-
Use this skill to design or repair Truthmark area structure.
|
|
3021
|
+
return `Use this skill to design or repair Truthmark area structure.
|
|
2784
3022
|
Invocations: ${TRUTH_STRUCTURE_EXPLICIT_INVOCATIONS}
|
|
2785
3023
|
Truth Structure is agent-native:
|
|
2786
3024
|
- inspect repository layout, current docs, Truthmark config and route files when present, and relevant code directly
|
|
2787
|
-
-
|
|
3025
|
+
- Evidence authority:
|
|
3026
|
+
${renderBulletBlock(EVIDENCE_AUTHORITY_INSTRUCTIONS)}
|
|
3027
|
+
- Lane classification:
|
|
3028
|
+
${renderLaneClassificationRuleBlock(config)}
|
|
2788
3029
|
- inspect the configured root route index at ${config.truthmark.paths.routesIndex} and relevant child route files under ${config.truthmark.paths.routeAreasRoot}/ when they exist
|
|
2789
3030
|
- define areas by product or behavior ownership, not by mechanical directory mirroring
|
|
2790
3031
|
- create or repair ${config.truthmark.paths.routesIndex}
|
|
2791
3032
|
- create starter truth docs when useful and when they belong in the canonical current-truth surface
|
|
2792
|
-
- Starter truth docs must use closed YAML frontmatter bounded by opening and closing --- lines; include status,
|
|
2793
|
-
- Starter truth docs must
|
|
3033
|
+
- Starter truth docs must use closed YAML frontmatter bounded by opening and closing --- lines; include status, truth_kind, and last_reviewed inside that frontmatter. Put source references in the final ## Source References section, not in frontmatter.
|
|
3034
|
+
- Starter truth docs must use lane-specific templates and keep product and engineering truth in separate files.
|
|
2794
3035
|
${subagentMode}
|
|
2795
3036
|
${FEATURE_DOC_TEMPLATE_INSTRUCTIONS}
|
|
2796
|
-
- use ${
|
|
3037
|
+
- use ${productTruthRoot}/** for product truth destinations
|
|
3038
|
+
- use ${engineeringTruthRoot}/** for engineering truth destinations
|
|
2797
3039
|
- use only canonical current-truth destinations for starter truth docs
|
|
2798
|
-
- keep active Product Decisions
|
|
3040
|
+
- keep active Product Decisions in product truth and Engineering Decisions in engineering truth
|
|
2799
3041
|
- preserve unrelated authored content
|
|
2800
3042
|
## New area setup
|
|
2801
3043
|
Use when a user asks to onboard a new code area into Truthmark, a new package, controller, domain, or product area lacks bounded truth ownership, or a new product area needs routing and starter truth docs.
|
|
@@ -2813,7 +3055,7 @@ Do not:
|
|
|
2813
3055
|
- do not create generic catch-all docs
|
|
2814
3056
|
- do not treat README files as Sync targets
|
|
2815
3057
|
## Topology Governance
|
|
2816
|
-
Truth Structure owns documentation topology. Do not depend on humans to manually organize ${
|
|
3058
|
+
Truth Structure owns documentation topology, lane splits, decision relocation, and relationship repair. Do not depend on humans to manually organize ${productTruthRoot} or ${engineeringTruthRoot}. Treat both configured lane roots as managed semantic roots.
|
|
2817
3059
|
Inspect controllers, routes, handlers, services, packages, tests, existing truth docs, and route files; infer product and domain ownership from behavior boundaries, not from mechanical directory mirroring.
|
|
2818
3060
|
When topology pressure exists, repair structure before creating or extending truth docs.
|
|
2819
3061
|
${renderTruthDocOwnershipGateSection(
|
|
@@ -2825,7 +3067,7 @@ Topology pressure signals:
|
|
|
2825
3067
|
- one area maps broad code such as src/**, app/**, server/**, services/**, or packages/**
|
|
2826
3068
|
- one area maps multiple unrelated controllers, route groups, services, or bounded contexts
|
|
2827
3069
|
- one truth doc owns unrelated behaviors or unrelated endpoint families
|
|
2828
|
-
-
|
|
3070
|
+
- either configured lane root has many direct non-index docs
|
|
2829
3071
|
- a changed controller, route, or service cannot map to a specific behavior doc
|
|
2830
3072
|
- Truth Sync would need to create a new generic truth doc because routing is too broad
|
|
2831
3073
|
- endpoint or controller names reveal domains missing from ${config.truthmark.paths.routeAreasRoot}/**
|
|
@@ -2838,9 +3080,10 @@ Repair rules:
|
|
|
2838
3080
|
- split broad, overloaded, or catch-all areas into behavior-owned child route files
|
|
2839
3081
|
- split mixed-owner truth docs into bounded owner docs before adding new behavior claims
|
|
2840
3082
|
- create route files under ${config.truthmark.paths.routeAreasRoot}/ when a product/domain boundary is clear
|
|
2841
|
-
- create behavior truth docs under
|
|
3083
|
+
- create engineering behavior truth docs under ${engineeringTruthRoot} only when behavior lacks a current doc
|
|
3084
|
+
- create product truth docs under ${productTruthRoot} only when product promise, boundary, rationale, or user-visible capability truth is in scope
|
|
2842
3085
|
- README.md files are indexes, not Truth Sync targets
|
|
2843
|
-
- prefer bounded
|
|
3086
|
+
- prefer bounded product docs under product/capabilities or product/decisions and engineering docs under engineering/<kind>/<surface>.md
|
|
2844
3087
|
- keep behavior truth docs behavior-oriented, not endpoint-oriented
|
|
2845
3088
|
- keep API endpoint details in the nearest contract truth doc when such a doc exists
|
|
2846
3089
|
- update routing so future Truth Sync can target small docs
|
|
@@ -2851,17 +3094,15 @@ ${renderTruthDocRestructureGateSection(
|
|
|
2851
3094
|
)}
|
|
2852
3095
|
${renderTopologyEvidenceGateSection()}
|
|
2853
3096
|
${ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS}
|
|
2854
|
-
- Do not finish topology repair with
|
|
2855
|
-
- If an existing canonical doc
|
|
3097
|
+
- Do not finish topology repair with mixed product/engineering authority in a single canonical truth doc.
|
|
3098
|
+
- If an existing canonical doc has wrong-lane sections, split or move them into the correct product or engineering lane.
|
|
2856
3099
|
Portable fallback:
|
|
2857
3100
|
- If this skill surface is unavailable, perform the same workflow directly from committed repository files.
|
|
2858
3101
|
- Do not require the truthmark CLI.
|
|
2859
3102
|
- Inspect .truthmark/config.yml and configured route files only when they exist; then inspect canonical docs and representative implementation code.
|
|
2860
3103
|
- Use a subagent only when the host supports that pattern; otherwise perform the topology repair inline.
|
|
2861
3104
|
${renderHierarchySummary(config)}
|
|
2862
|
-
${DECISION_TRUTH_INSTRUCTIONS}
|
|
2863
|
-
Report completion in this shape:
|
|
2864
|
-
${renderMarkdownExample4(renderTruthStructureReportExample(config))}`;
|
|
3105
|
+
${DECISION_TRUTH_INSTRUCTIONS}`;
|
|
2865
3106
|
};
|
|
2866
3107
|
|
|
2867
3108
|
// src/sync/report.ts
|
|
@@ -2975,13 +3216,8 @@ var renderTruthSyncBlockedReport = (input) => {
|
|
|
2975
3216
|
|
|
2976
3217
|
// src/agents/truth-sync.ts
|
|
2977
3218
|
var TRUTH_SYNC_EXPLICIT_INVOCATIONS = "OpenCode /skill truthmark-sync; Codex /truthmark-sync or $truthmark-sync; Claude Code /truthmark-sync; GitHub Copilot /truthmark-sync; Gemini CLI /truthmark:sync.";
|
|
2978
|
-
var
|
|
2979
|
-
return ["```md", content, "```"].join("\n");
|
|
2980
|
-
};
|
|
2981
|
-
var renderTruthSyncSkillBody = (config = defaultAgentConfig(), options = {}) => {
|
|
2982
|
-
const truthDocsRoot = resolveTruthDocsRoot(config);
|
|
3219
|
+
var renderTruthSyncProcedureBody = (config = defaultAgentConfig(), options = {}) => {
|
|
2983
3220
|
const workflow = getTruthmarkWorkflow("truthmark-sync");
|
|
2984
|
-
const helperScripts = ["validate-write-lease: skipped, no write lease used"];
|
|
2985
3221
|
const claudeSubagentMode = options.includeClaudeSubagentMode ? `${renderClaudeSubagentModeSection(
|
|
2986
3222
|
workflow.subagents ?? [],
|
|
2987
3223
|
"Parent agent owns Truth Sync acceptance, lease validation, and final report",
|
|
@@ -3007,15 +3243,7 @@ var renderTruthSyncSkillBody = (config = defaultAgentConfig(), options = {}) =>
|
|
|
3007
3243
|
)}
|
|
3008
3244
|
` : "";
|
|
3009
3245
|
const subagentMode = `${claudeSubagentMode}${codexSubagentMode}${copilotCustomAgentMode}${openCodeSubagentMode}`;
|
|
3010
|
-
return
|
|
3011
|
-
name: truthmark-sync
|
|
3012
|
-
description: ${workflow.description}
|
|
3013
|
-
argument-hint: Optional changed-code area, truth-doc area, or sync focus
|
|
3014
|
-
user-invocable: true
|
|
3015
|
-
truthmark-version: ${TRUTHMARK_VERSION}
|
|
3016
|
-
---
|
|
3017
|
-
|
|
3018
|
-
Use this skill automatically before finishing when functional code changed since the last successful Truth Sync. Also run it immediately when the user explicitly invokes Truth Sync.
|
|
3246
|
+
return `Use this skill automatically before finishing when functional code changed since the last successful Truth Sync. Also run it immediately when the user explicitly invokes Truth Sync.
|
|
3019
3247
|
Invocations: ${TRUTH_SYNC_EXPLICIT_INVOCATIONS}
|
|
3020
3248
|
Explicit invocation runs immediately. Later functional-code changes reopen the finish-time requirement, and an earlier explicit run satisfies the finish gate only if no later functional-code changes occur.
|
|
3021
3249
|
Skip when changes are documentation-only, formatting-only, clearly behavior-preserving renames with no truth impact, when no Truthmark config exists yet, or when there are no functional code changes.
|
|
@@ -3023,9 +3251,13 @@ Parent workflow:
|
|
|
3023
3251
|
1. Inspect git status, staged changes, unstaged changes, and untracked files directly.
|
|
3024
3252
|
2. Inspect .truthmark/config.yml and configured route files only when they exist; then inspect relevant canonical docs.
|
|
3025
3253
|
3. Identify functional-code changes and the nearest truth docs or routing repairs.
|
|
3026
|
-
4.
|
|
3027
|
-
|
|
3028
|
-
|
|
3254
|
+
4. Evidence authority:
|
|
3255
|
+
${renderBulletBlock(EVIDENCE_AUTHORITY_INSTRUCTIONS)}
|
|
3256
|
+
5. Lane classification gate:
|
|
3257
|
+
${renderLaneClassificationRuleBlock(config)}
|
|
3258
|
+
6. Update engineering truth first after code changes. Update product truth only when implemented user-visible product promise or capability boundary changed and explicit source/user evidence supports it; otherwise report product-lane review needed.
|
|
3259
|
+
7. Code verification is parent-owned: follow repository instructions and task context, and report what ran or why it did not run.
|
|
3260
|
+
8. Dispatch bounded Truth Sync workers only when the host supports subagent dispatch and the acting agent chooses that path; otherwise execute the same sync task inline.
|
|
3029
3261
|
${subagentMode}Topology quality gate:
|
|
3030
3262
|
- before updating truth docs, verify the changed code resolves to a specific behavior-owned area and bounded truth owner
|
|
3031
3263
|
- if routing is missing, stale, broad, overloaded, catch-all route only, or cannot map changed code to a bounded truth owner, do not create another generic truth doc
|
|
@@ -3035,6 +3267,7 @@ ${subagentMode}Topology quality gate:
|
|
|
3035
3267
|
- README.md files are indexes, not Truth Sync targets
|
|
3036
3268
|
- must not append behavior details to a README.md index
|
|
3037
3269
|
- create or update a bounded leaf truth doc when behavior changes do not fit an existing leaf doc
|
|
3270
|
+
- write engineering truth under ${config.truthmark.paths.engineeringTruthRoot}; product truth updates under ${config.truthmark.paths.productTruthRoot} are allowed only for explicit current product behavior changes
|
|
3038
3271
|
${renderTruthDocOwnershipGateSection(
|
|
3039
3272
|
"changed functional files and impacted truth docs",
|
|
3040
3273
|
"if an impacted doc is broad, mixed-owner, index-like, or the update spans independent behavior owners, run Truth Structure before syncing when safe and in scope; otherwise block and recommend Truth Structure"
|
|
@@ -3054,7 +3287,7 @@ Optional validation tooling:
|
|
|
3054
3287
|
- you may run truthmark check when local tooling is available
|
|
3055
3288
|
- do not require the truthmark binary; direct checkout inspection is the canonical path
|
|
3056
3289
|
- optional validation must not replace agent judgment about docs and routing
|
|
3057
|
-
- update Product Decisions and
|
|
3290
|
+
- update Product Decisions only in product truth and Engineering Decisions only in engineering truth when evidence supports the lane-specific decision change
|
|
3058
3291
|
Helper status reporting:
|
|
3059
3292
|
- Validate the report body before adding this validator's own success status; the body may omit \`validate-sync-report\` while validation is pending.
|
|
3060
3293
|
- After \`truthmark validate sync-report <report-file> --json\` returns \`data.validation.ok: true\`, append or update \`validate-sync-report: ran, passed\` in the final report.
|
|
@@ -3071,32 +3304,7 @@ Parent post-sync verification:
|
|
|
3071
3304
|
- validate the final report against the structured Truth Sync report contract, including Claim, indented Evidence, and Result values supported, narrowed, removed, or blocked under Evidence checked
|
|
3072
3305
|
- verify the updated docs correspond to the reviewed changed-code surface
|
|
3073
3306
|
- verify the final report records ownership review, structure requirement, split, restructure, or blocked reason when the ownership gate fired
|
|
3074
|
-
- blocked outcomes must preserve the working tree as-is: no rollback, no post-block cleanup edits, and manual-review reporting of any remaining files
|
|
3075
|
-
Report completion in this shape:
|
|
3076
|
-
${renderMarkdownExample5(
|
|
3077
|
-
renderTruthSyncCompletedReport({
|
|
3078
|
-
changedCode: ["src/auth/session.ts"],
|
|
3079
|
-
ownershipReviewed: [config.truthmark.paths.routesIndex],
|
|
3080
|
-
truthDocsUpdated: [`${truthDocsRoot}/repository/overview.md`],
|
|
3081
|
-
evidenceChecked: [
|
|
3082
|
-
{
|
|
3083
|
-
claim: "Session timeout behavior is documented in the mapped repository truth doc.",
|
|
3084
|
-
evidence: ["src/auth/session.ts:12", `${config.truthmark.paths.routesIndex}:11`],
|
|
3085
|
-
result: "supported"
|
|
3086
|
-
}
|
|
3087
|
-
],
|
|
3088
|
-
helperScripts,
|
|
3089
|
-
notes: ["Updated session timeout behavior."]
|
|
3090
|
-
})
|
|
3091
|
-
)}
|
|
3092
|
-
Blocked report example:
|
|
3093
|
-
${renderMarkdownExample5(
|
|
3094
|
-
renderTruthSyncBlockedReport({
|
|
3095
|
-
reason: "routing repair is not allowed",
|
|
3096
|
-
manualReviewFiles: [config.truthmark.paths.routesIndex],
|
|
3097
|
-
nextAction: "update routing metadata and rerun Truth Sync"
|
|
3098
|
-
})
|
|
3099
|
-
)}`;
|
|
3307
|
+
- blocked outcomes must preserve the working tree as-is: no rollback, no post-block cleanup edits, and manual-review reporting of any remaining files`;
|
|
3100
3308
|
};
|
|
3101
3309
|
|
|
3102
3310
|
// src/agents/write-lease.ts
|
|
@@ -3182,6 +3390,30 @@ description: '${description}'
|
|
|
3182
3390
|
${prompt}
|
|
3183
3391
|
`;
|
|
3184
3392
|
};
|
|
3393
|
+
var workflowSupportFiles = (workflowId) => {
|
|
3394
|
+
const workflow = getTruthmarkWorkflow(workflowId);
|
|
3395
|
+
const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
|
|
3396
|
+
const hasSubagentSupport = definition.parentRule !== void 0 && ((workflow.subagents?.length ?? 0) > 0 || (workflow.writeSubagents?.length ?? 0) > 0);
|
|
3397
|
+
const hasHelperSupport = (workflow.helpers?.length ?? 0) > 0;
|
|
3398
|
+
return [
|
|
3399
|
+
"support/procedure.md",
|
|
3400
|
+
"support/report-template.md",
|
|
3401
|
+
...hasSubagentSupport ? ["support/subagents-and-leases.md"] : [],
|
|
3402
|
+
...hasHelperSupport ? ["helper-manifest.yml", "support/helper-policy.md"] : []
|
|
3403
|
+
];
|
|
3404
|
+
};
|
|
3405
|
+
var renderWorkflowCommandAdapterInstructions = (workflowId, root, hostName, surfaceKind) => {
|
|
3406
|
+
const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
|
|
3407
|
+
const canonicalFiles = ["SKILL.md", ...workflowSupportFiles(workflowId)].map((supportFile) => `- ${root}/${supportFile}`).join("\n");
|
|
3408
|
+
return `This ${surfaceKind} is the ${hostName} entrypoint for ${definition.title}.
|
|
3409
|
+
|
|
3410
|
+
Do not invoke another Truthmark command from here.
|
|
3411
|
+
|
|
3412
|
+
Read these host-local files in order only as needed:
|
|
3413
|
+
${canonicalFiles}
|
|
3414
|
+
|
|
3415
|
+
If skill entrypoints are unavailable, use the host's direct evidence-first manual fallback procedure.`;
|
|
3416
|
+
};
|
|
3185
3417
|
var renderTomlString = (value) => {
|
|
3186
3418
|
return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"')}"`;
|
|
3187
3419
|
};
|
|
@@ -3246,7 +3478,7 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
|
|
|
3246
3478
|
use: () => "Use this skill only when the user explicitly asks to preview Truthmark routing or workflow choice before edits.",
|
|
3247
3479
|
quickRules: (config) => [
|
|
3248
3480
|
"Follow repository instruction files that exist in this checkout; do not assume any optional policy path exists.",
|
|
3249
|
-
`Inspect .truthmark/config.yml and
|
|
3481
|
+
`Inspect .truthmark/config.yml and the root route index (${config.truthmark.paths.routesIndex}) first when present; then inspect only child route files under ${config.truthmark.paths.routeAreasRoot}/ that are relevant to the selected scope or changed paths, plus the truth docs or implementation files needed to preview ownership.`,
|
|
3250
3482
|
"Truth Preview is read-only; this report is intended, not authorized.",
|
|
3251
3483
|
"must not edit files and must not issue write leases; do not run Truth Sync automatically, replace Truth Check, claim final correctness, or mutate code.",
|
|
3252
3484
|
"Use optional read-only route-auditor evidence only when it reduces context or clarifies ownership.",
|
|
@@ -3302,22 +3534,110 @@ var WORKFLOW_PACKAGE_DEFINITIONS = {
|
|
|
3302
3534
|
]
|
|
3303
3535
|
}
|
|
3304
3536
|
};
|
|
3305
|
-
var
|
|
3306
|
-
return
|
|
3537
|
+
var renderMarkdownExample = (content) => {
|
|
3538
|
+
return ["```md", content, "```"].join("\n");
|
|
3307
3539
|
};
|
|
3308
|
-
var
|
|
3309
|
-
const
|
|
3310
|
-
const
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3540
|
+
var renderWorkflowReportTemplate = (workflowId, config) => {
|
|
3541
|
+
const productTruthRoot = resolveProductTruthRoot(config);
|
|
3542
|
+
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
3543
|
+
switch (workflowId) {
|
|
3544
|
+
case "truthmark-structure":
|
|
3545
|
+
return `Report completion in this shape:
|
|
3546
|
+
${renderMarkdownExample(
|
|
3547
|
+
renderTruthStructureReportExample(config)
|
|
3548
|
+
)}`;
|
|
3549
|
+
case "truthmark-document":
|
|
3550
|
+
return `Report completion in this shape:
|
|
3551
|
+
${renderMarkdownExample(
|
|
3552
|
+
renderTruthDocumentReportExample(config)
|
|
3553
|
+
)}`;
|
|
3554
|
+
case "truthmark-sync":
|
|
3555
|
+
return `Report completion in this shape:
|
|
3556
|
+
${renderMarkdownExample(
|
|
3557
|
+
renderTruthSyncCompletedReport({
|
|
3558
|
+
changedCode: ["src/auth/session.ts"],
|
|
3559
|
+
ownershipReviewed: [config.truthmark.paths.routesIndex],
|
|
3560
|
+
truthDocsUpdated: [`${engineeringTruthRoot}/repository/overview.md`],
|
|
3561
|
+
evidenceChecked: [
|
|
3562
|
+
{
|
|
3563
|
+
claim: "Session timeout behavior is documented in the mapped repository truth doc.",
|
|
3564
|
+
evidence: [
|
|
3565
|
+
"src/auth/session.ts:12",
|
|
3566
|
+
`${config.truthmark.paths.routesIndex}:11`
|
|
3567
|
+
],
|
|
3568
|
+
result: "supported"
|
|
3569
|
+
}
|
|
3570
|
+
],
|
|
3571
|
+
helperScripts: [
|
|
3572
|
+
"validate-write-lease: skipped, no write lease used"
|
|
3573
|
+
],
|
|
3574
|
+
notes: ["Updated session timeout behavior."]
|
|
3575
|
+
})
|
|
3576
|
+
)}
|
|
3577
|
+
Blocked report example:
|
|
3578
|
+
${renderMarkdownExample(
|
|
3579
|
+
renderTruthSyncBlockedReport({
|
|
3580
|
+
reason: "routing repair is not allowed",
|
|
3581
|
+
manualReviewFiles: [config.truthmark.paths.routesIndex],
|
|
3582
|
+
nextAction: "update routing metadata and rerun Truth Sync"
|
|
3583
|
+
})
|
|
3584
|
+
)}`;
|
|
3585
|
+
case "truthmark-preview":
|
|
3586
|
+
return `Report completion in this shape:
|
|
3587
|
+
${renderMarkdownExample(
|
|
3588
|
+
renderTruthPreviewReportExample(config)
|
|
3589
|
+
)}`;
|
|
3590
|
+
case "truthmark-realize":
|
|
3591
|
+
return `Report completion in this shape:
|
|
3592
|
+
|
|
3593
|
+
${renderMarkdownExample(`Truth Realize: completed
|
|
3594
|
+
|
|
3595
|
+
Truth docs used:
|
|
3596
|
+
- ${productTruthRoot}/capabilities/authentication-session.md
|
|
3597
|
+
- ${engineeringTruthRoot}/behaviors/authentication-session.md
|
|
3598
|
+
|
|
3599
|
+
Code updated:
|
|
3600
|
+
- src/auth/session.ts
|
|
3601
|
+
|
|
3602
|
+
Verification:
|
|
3603
|
+
- npm test -- auth`)}`;
|
|
3604
|
+
case "truthmark-check":
|
|
3605
|
+
return `Report completion in this shape:
|
|
3606
|
+
|
|
3607
|
+
${renderMarkdownExample(
|
|
3608
|
+
renderTruthCheckReportExample(config)
|
|
3609
|
+
)}`;
|
|
3610
|
+
case "truthmark-portal":
|
|
3611
|
+
return `Report completion in this shape:
|
|
3612
|
+
|
|
3613
|
+
${renderMarkdownExample(`Truthmark Portal: completed
|
|
3614
|
+
|
|
3615
|
+
Output path:
|
|
3616
|
+
- ${config.truthmark.paths.portalOutput}
|
|
3617
|
+
|
|
3618
|
+
Page count:
|
|
3619
|
+
- <count>
|
|
3620
|
+
|
|
3621
|
+
Diagrams/assets:
|
|
3622
|
+
- <generated diagrams/assets or none>
|
|
3623
|
+
|
|
3624
|
+
Source docs reviewed:
|
|
3625
|
+
- <source markdown paths>
|
|
3626
|
+
|
|
3627
|
+
Skipped/ambiguous docs:
|
|
3628
|
+
- <paths and reason, or none>
|
|
3629
|
+
|
|
3630
|
+
Validation:
|
|
3631
|
+
- <checks performed>
|
|
3632
|
+
|
|
3633
|
+
Markdown canonical statement:
|
|
3634
|
+
- Markdown remains canonical; generated Portal HTML is non-canonical presentation only.`)}`;
|
|
3317
3635
|
}
|
|
3636
|
+
};
|
|
3637
|
+
var renderWorkflowSupportParts = (workflowId, config) => {
|
|
3318
3638
|
return {
|
|
3319
|
-
procedure:
|
|
3320
|
-
reportTemplate:
|
|
3639
|
+
procedure: renderWorkflowProcedure(workflowId, config),
|
|
3640
|
+
reportTemplate: renderWorkflowReportTemplate(workflowId, config)
|
|
3321
3641
|
};
|
|
3322
3642
|
};
|
|
3323
3643
|
var renderSkillSupportFile = (title, body) => {
|
|
@@ -3378,22 +3698,22 @@ Helper scripts:
|
|
|
3378
3698
|
\`\`\``
|
|
3379
3699
|
);
|
|
3380
3700
|
};
|
|
3381
|
-
var
|
|
3701
|
+
var renderWorkflowProcedure = (workflowId, config) => {
|
|
3382
3702
|
switch (workflowId) {
|
|
3383
3703
|
case "truthmark-structure":
|
|
3384
|
-
return
|
|
3704
|
+
return renderTruthStructureProcedureBody(config);
|
|
3385
3705
|
case "truthmark-document":
|
|
3386
|
-
return
|
|
3706
|
+
return renderTruthDocumentProcedureBody(config);
|
|
3387
3707
|
case "truthmark-sync":
|
|
3388
|
-
return
|
|
3708
|
+
return renderTruthSyncProcedureBody(config);
|
|
3389
3709
|
case "truthmark-preview":
|
|
3390
|
-
return
|
|
3710
|
+
return renderTruthPreviewProcedureBody(config);
|
|
3391
3711
|
case "truthmark-realize":
|
|
3392
|
-
return
|
|
3712
|
+
return renderTruthmarkRealizeProcedureBody(config);
|
|
3393
3713
|
case "truthmark-check":
|
|
3394
|
-
return
|
|
3714
|
+
return renderTruthCheckProcedureBody(config);
|
|
3395
3715
|
case "truthmark-portal":
|
|
3396
|
-
return
|
|
3716
|
+
return renderTruthmarkPortalProcedureBody(config);
|
|
3397
3717
|
}
|
|
3398
3718
|
};
|
|
3399
3719
|
var renderWorkflowEntrypoint = (workflowId, config, supportFiles, host) => {
|
|
@@ -3494,17 +3814,13 @@ var renderTruthmarkSkillPackage = ({
|
|
|
3494
3814
|
}) => {
|
|
3495
3815
|
const skillDirectory = skillPath.replace(/\/SKILL\.md$/u, "");
|
|
3496
3816
|
const supportDirectory = `${skillDirectory}/support`;
|
|
3497
|
-
const { procedure, reportTemplate } =
|
|
3498
|
-
|
|
3817
|
+
const { procedure, reportTemplate } = renderWorkflowSupportParts(
|
|
3818
|
+
workflowId,
|
|
3819
|
+
config
|
|
3499
3820
|
);
|
|
3500
|
-
const subagents = renderWorkflowSubagentSupport(workflowId, host);
|
|
3501
|
-
const helpers = getTruthmarkWorkflow(workflowId).helpers ?? [];
|
|
3502
|
-
const supportFiles =
|
|
3503
|
-
"support/procedure.md",
|
|
3504
|
-
"support/report-template.md",
|
|
3505
|
-
...subagents === void 0 ? [] : ["support/subagents-and-leases.md"],
|
|
3506
|
-
...helpers.length === 0 ? [] : ["helper-manifest.yml", "support/helper-policy.md"]
|
|
3507
|
-
];
|
|
3821
|
+
const subagents = renderWorkflowSubagentSupport(workflowId, host);
|
|
3822
|
+
const helpers = getTruthmarkWorkflow(workflowId).helpers ?? [];
|
|
3823
|
+
const supportFiles = workflowSupportFiles(workflowId);
|
|
3508
3824
|
const definition = WORKFLOW_PACKAGE_DEFINITIONS[workflowId];
|
|
3509
3825
|
const files = [
|
|
3510
3826
|
{
|
|
@@ -3549,16 +3865,19 @@ var renderTruthmarkSkillPackage = ({
|
|
|
3549
3865
|
}
|
|
3550
3866
|
return files;
|
|
3551
3867
|
};
|
|
3552
|
-
var normalizeOpenCodePermissionPath = (
|
|
3553
|
-
const normalized =
|
|
3868
|
+
var normalizeOpenCodePermissionPath = (path14) => {
|
|
3869
|
+
const normalized = path14.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
|
|
3554
3870
|
return normalized === "" ? "." : normalized;
|
|
3555
3871
|
};
|
|
3556
3872
|
var appendOpenCodePermissionGlob = (root, glob) => {
|
|
3557
3873
|
return root === "." ? glob.replace(/^\//u, "") : `${root}${glob}`;
|
|
3558
3874
|
};
|
|
3559
3875
|
var renderOpenCodeWriterEditAllowRules = (config) => {
|
|
3560
|
-
const
|
|
3561
|
-
|
|
3876
|
+
const truthDocRoots = Array.from(
|
|
3877
|
+
/* @__PURE__ */ new Set([
|
|
3878
|
+
normalizeOpenCodePermissionPath(resolveProductTruthRoot(config)),
|
|
3879
|
+
normalizeOpenCodePermissionPath(resolveEngineeringTruthRoot(config))
|
|
3880
|
+
])
|
|
3562
3881
|
);
|
|
3563
3882
|
const rootRouteIndex = normalizeOpenCodePermissionPath(
|
|
3564
3883
|
config.truthmark.paths.routesIndex
|
|
@@ -3567,7 +3886,7 @@ var renderOpenCodeWriterEditAllowRules = (config) => {
|
|
|
3567
3886
|
config.truthmark.paths.routeAreasRoot
|
|
3568
3887
|
);
|
|
3569
3888
|
const allowedPatterns = [
|
|
3570
|
-
appendOpenCodePermissionGlob(
|
|
3889
|
+
...truthDocRoots.map((root) => appendOpenCodePermissionGlob(root, "/**")),
|
|
3571
3890
|
rootRouteIndex,
|
|
3572
3891
|
appendOpenCodePermissionGlob(areaFilesRoot, "/**/*.md")
|
|
3573
3892
|
];
|
|
@@ -3590,7 +3909,14 @@ var TRUTHMARK_SUBAGENT_PROFILES = {
|
|
|
3590
3909
|
instructions: `Stay read-only.
|
|
3591
3910
|
Audit one bounded Truthmark route, area, or doc shard assigned by the parent.
|
|
3592
3911
|
Inspect .truthmark/config.yml and route files only when they exist; then inspect mapped truth docs and relevant implementation files directly.
|
|
3912
|
+
Use a route-first bounded strategy: narrow audits inspect only the routed area and directly linked counterpart docs; root-wide health first builds a cheap route-map/index from route files, then inspects only mismatches and linked leaves.
|
|
3593
3913
|
Find missing, stale, broad, overloaded, catch-all, mixed-owner, or unrouteable ownership.
|
|
3914
|
+
Validate route ownership against lane-specific roots and route kind:
|
|
3915
|
+
- confirm mapped truth docs resolve to the correct lane root (product or engineering) for their kind
|
|
3916
|
+
- flag mismatch between assigned route kind and resolved doc kind (for example, product-capability routed to engineering paths)
|
|
3917
|
+
- verify route-doc linkage for lane pairings via realized_by and realizes before recommending edits
|
|
3918
|
+
- inspect product counterparts for engineering docs only when route YAML claims a product relationship, or when the parent explicitly asks for user-visible product coverage
|
|
3919
|
+
- treat missing product links for user-visible engineering docs as a second-pass diagnostic, not a default full-document read.
|
|
3594
3920
|
Do not edit files, stage changes, or propose broad rewrites.
|
|
3595
3921
|
Return JSON only with keys: scope, filesReviewed, findings, evidence, confidence, recommendedWorkflow, notes.
|
|
3596
3922
|
recommendedWorkflow must be one of: none, truthmark-document, truthmark-structure.`
|
|
@@ -3614,7 +3940,7 @@ Return JSON only with keys: scope, filesReviewed, claimsChecked, evidence, unsup
|
|
|
3614
3940
|
description: "Read-only Truthmark doc reviewer for shape, decision, rationale, and evidence hygiene.",
|
|
3615
3941
|
nicknameCandidates: ["Doc Audit", "Doc Shape", "Doc Check"],
|
|
3616
3942
|
instructions: `Stay read-only.
|
|
3617
|
-
Review assigned canonical truth docs for frontmatter,
|
|
3943
|
+
Review assigned canonical truth docs for compact frontmatter, required template sections, final Source References entries, Evidence checked entries, and lane-appropriate decision sections (Product Decisions in product truth, Engineering Decisions in engineering truth).
|
|
3618
3944
|
Flag README.md files used as behavior truth targets, mixed-owner docs, and shape repairs that should move to Truth Structure.
|
|
3619
3945
|
Do not edit files, stage changes, or rewrite docs.
|
|
3620
3946
|
Return JSON only with keys: scope, filesReviewed, findings, evidence, confidence, recommendedWorkflow, notes.
|
|
@@ -4027,18 +4353,8 @@ truthmark:
|
|
|
4027
4353
|
refresh_command: "truthmark init"
|
|
4028
4354
|
`;
|
|
4029
4355
|
};
|
|
4030
|
-
var
|
|
4031
|
-
|
|
4032
|
-
const workflow = getTruthmarkWorkflow("truthmark-realize");
|
|
4033
|
-
return `---
|
|
4034
|
-
name: truthmark-realize
|
|
4035
|
-
description: ${workflow.description}
|
|
4036
|
-
argument-hint: Optional truth doc path, area, or desired code behavior to realize
|
|
4037
|
-
user-invocable: true
|
|
4038
|
-
truthmark-version: ${TRUTHMARK_VERSION}
|
|
4039
|
-
---
|
|
4040
|
-
|
|
4041
|
-
# Truthmark Realize
|
|
4356
|
+
var renderTruthmarkRealizeProcedureBody = (config = defaultAgentConfig()) => {
|
|
4357
|
+
return `# Truthmark Realize
|
|
4042
4358
|
|
|
4043
4359
|
Use this skill only when the user explicitly asks to realize truth docs into code.
|
|
4044
4360
|
|
|
@@ -4069,23 +4385,7 @@ Read and write boundaries:
|
|
|
4069
4385
|
|
|
4070
4386
|
- may read truth docs, routing docs, and relevant functional code
|
|
4071
4387
|
- may write functional code only
|
|
4072
|
-
- must not edit truth docs or truth routing while realizing those docs
|
|
4073
|
-
|
|
4074
|
-
Report completion in this shape:
|
|
4075
|
-
|
|
4076
|
-
\`\`\`md
|
|
4077
|
-
Truth Realize: completed
|
|
4078
|
-
|
|
4079
|
-
Truth docs used:
|
|
4080
|
-
- ${truthDocsRoot}/authentication/session-timeout.md
|
|
4081
|
-
|
|
4082
|
-
Code updated:
|
|
4083
|
-
- src/auth/session.ts
|
|
4084
|
-
|
|
4085
|
-
Verification:
|
|
4086
|
-
- npm test -- auth
|
|
4087
|
-
\`\`\`
|
|
4088
|
-
`;
|
|
4388
|
+
- must not edit truth docs or truth routing while realizing those docs`;
|
|
4089
4389
|
};
|
|
4090
4390
|
var renderTruthmarkRealizeSkillMetadata = () => {
|
|
4091
4391
|
const workflow = getTruthmarkWorkflow("truthmark-realize");
|
|
@@ -4147,110 +4447,126 @@ truthmark:
|
|
|
4147
4447
|
refresh_command: "truthmark init"
|
|
4148
4448
|
`;
|
|
4149
4449
|
};
|
|
4150
|
-
var
|
|
4151
|
-
const workflow = getTruthmarkWorkflow(
|
|
4450
|
+
var renderGeminiWorkflowCommand = (workflowId, root) => {
|
|
4451
|
+
const workflow = getTruthmarkWorkflow(workflowId);
|
|
4152
4452
|
return renderGeminiCommand(
|
|
4153
4453
|
workflow.description,
|
|
4154
|
-
|
|
4454
|
+
renderWorkflowCommandAdapterInstructions(
|
|
4455
|
+
workflowId,
|
|
4456
|
+
root,
|
|
4457
|
+
"Gemini CLI",
|
|
4458
|
+
"command"
|
|
4459
|
+
)
|
|
4155
4460
|
);
|
|
4156
4461
|
};
|
|
4157
|
-
var
|
|
4158
|
-
const workflow = getTruthmarkWorkflow(
|
|
4159
|
-
return
|
|
4462
|
+
var renderCopilotWorkflowPrompt = (workflowId, root) => {
|
|
4463
|
+
const workflow = getTruthmarkWorkflow(workflowId);
|
|
4464
|
+
return renderCopilotPromptFile(
|
|
4160
4465
|
workflow.description,
|
|
4161
|
-
|
|
4466
|
+
renderWorkflowCommandAdapterInstructions(
|
|
4467
|
+
workflowId,
|
|
4468
|
+
root,
|
|
4469
|
+
"GitHub Copilot",
|
|
4470
|
+
"prompt"
|
|
4471
|
+
)
|
|
4472
|
+
);
|
|
4473
|
+
};
|
|
4474
|
+
var renderTruthmarkGeminiStructureCommand = (config = defaultAgentConfig()) => {
|
|
4475
|
+
void config;
|
|
4476
|
+
return renderGeminiWorkflowCommand(
|
|
4477
|
+
"truthmark-structure",
|
|
4478
|
+
".gemini/skills/truthmark-structure"
|
|
4479
|
+
);
|
|
4480
|
+
};
|
|
4481
|
+
var renderTruthmarkGeminiDocumentCommand = (config = defaultAgentConfig()) => {
|
|
4482
|
+
void config;
|
|
4483
|
+
return renderGeminiWorkflowCommand(
|
|
4484
|
+
"truthmark-document",
|
|
4485
|
+
".gemini/skills/truthmark-document"
|
|
4162
4486
|
);
|
|
4163
4487
|
};
|
|
4164
4488
|
var renderTruthmarkGeminiSyncCommand = (config = defaultAgentConfig()) => {
|
|
4165
|
-
|
|
4166
|
-
return
|
|
4167
|
-
|
|
4168
|
-
|
|
4489
|
+
void config;
|
|
4490
|
+
return renderGeminiWorkflowCommand(
|
|
4491
|
+
"truthmark-sync",
|
|
4492
|
+
".gemini/skills/truthmark-sync"
|
|
4169
4493
|
);
|
|
4170
4494
|
};
|
|
4171
4495
|
var renderTruthmarkGeminiRealizeCommand = (config = defaultAgentConfig()) => {
|
|
4172
|
-
|
|
4173
|
-
return
|
|
4174
|
-
|
|
4175
|
-
|
|
4496
|
+
void config;
|
|
4497
|
+
return renderGeminiWorkflowCommand(
|
|
4498
|
+
"truthmark-realize",
|
|
4499
|
+
".gemini/skills/truthmark-realize"
|
|
4176
4500
|
);
|
|
4177
4501
|
};
|
|
4178
4502
|
var renderTruthmarkGeminiCheckCommand = (config = defaultAgentConfig()) => {
|
|
4179
|
-
|
|
4180
|
-
return
|
|
4181
|
-
|
|
4182
|
-
|
|
4503
|
+
void config;
|
|
4504
|
+
return renderGeminiWorkflowCommand(
|
|
4505
|
+
"truthmark-check",
|
|
4506
|
+
".gemini/skills/truthmark-check"
|
|
4183
4507
|
);
|
|
4184
4508
|
};
|
|
4185
4509
|
var renderTruthmarkGeminiPreviewCommand = (config = defaultAgentConfig()) => {
|
|
4186
|
-
|
|
4187
|
-
return
|
|
4188
|
-
|
|
4189
|
-
|
|
4510
|
+
void config;
|
|
4511
|
+
return renderGeminiWorkflowCommand(
|
|
4512
|
+
"truthmark-preview",
|
|
4513
|
+
".gemini/skills/truthmark-preview"
|
|
4190
4514
|
);
|
|
4191
4515
|
};
|
|
4192
4516
|
var renderTruthmarkGeminiPortalCommand = (config = defaultAgentConfig()) => {
|
|
4193
|
-
|
|
4194
|
-
return
|
|
4195
|
-
|
|
4196
|
-
|
|
4517
|
+
void config;
|
|
4518
|
+
return renderGeminiWorkflowCommand(
|
|
4519
|
+
"truthmark-portal",
|
|
4520
|
+
".gemini/skills/truthmark-portal"
|
|
4197
4521
|
);
|
|
4198
4522
|
};
|
|
4199
4523
|
var renderTruthmarkCopilotStructurePrompt = (config = defaultAgentConfig()) => {
|
|
4200
|
-
|
|
4201
|
-
return
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
includeCopilotCustomAgentMode: true
|
|
4205
|
-
})
|
|
4524
|
+
void config;
|
|
4525
|
+
return renderCopilotWorkflowPrompt(
|
|
4526
|
+
"truthmark-structure",
|
|
4527
|
+
".github/skills/truthmark-structure"
|
|
4206
4528
|
);
|
|
4207
4529
|
};
|
|
4208
4530
|
var renderTruthmarkCopilotDocumentPrompt = (config = defaultAgentConfig()) => {
|
|
4209
|
-
|
|
4210
|
-
return
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
includeCopilotCustomAgentMode: true
|
|
4214
|
-
})
|
|
4531
|
+
void config;
|
|
4532
|
+
return renderCopilotWorkflowPrompt(
|
|
4533
|
+
"truthmark-document",
|
|
4534
|
+
".github/skills/truthmark-document"
|
|
4215
4535
|
);
|
|
4216
4536
|
};
|
|
4217
4537
|
var renderTruthmarkCopilotSyncPrompt = (config = defaultAgentConfig()) => {
|
|
4218
|
-
|
|
4219
|
-
return
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
includeCopilotCustomAgentMode: true
|
|
4223
|
-
})
|
|
4538
|
+
void config;
|
|
4539
|
+
return renderCopilotWorkflowPrompt(
|
|
4540
|
+
"truthmark-sync",
|
|
4541
|
+
".github/skills/truthmark-sync"
|
|
4224
4542
|
);
|
|
4225
4543
|
};
|
|
4226
4544
|
var renderTruthmarkCopilotRealizePrompt = (config = defaultAgentConfig()) => {
|
|
4227
|
-
|
|
4228
|
-
return
|
|
4229
|
-
|
|
4230
|
-
|
|
4545
|
+
void config;
|
|
4546
|
+
return renderCopilotWorkflowPrompt(
|
|
4547
|
+
"truthmark-realize",
|
|
4548
|
+
".github/skills/truthmark-realize"
|
|
4231
4549
|
);
|
|
4232
4550
|
};
|
|
4233
4551
|
var renderTruthmarkCopilotCheckPrompt = (config = defaultAgentConfig()) => {
|
|
4234
|
-
|
|
4235
|
-
return
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
includeCopilotCustomAgentMode: true
|
|
4239
|
-
})
|
|
4552
|
+
void config;
|
|
4553
|
+
return renderCopilotWorkflowPrompt(
|
|
4554
|
+
"truthmark-check",
|
|
4555
|
+
".github/skills/truthmark-check"
|
|
4240
4556
|
);
|
|
4241
4557
|
};
|
|
4242
4558
|
var renderTruthmarkCopilotPreviewPrompt = (config = defaultAgentConfig()) => {
|
|
4243
|
-
|
|
4244
|
-
return
|
|
4245
|
-
|
|
4246
|
-
|
|
4559
|
+
void config;
|
|
4560
|
+
return renderCopilotWorkflowPrompt(
|
|
4561
|
+
"truthmark-preview",
|
|
4562
|
+
".github/skills/truthmark-preview"
|
|
4247
4563
|
);
|
|
4248
4564
|
};
|
|
4249
4565
|
var renderTruthmarkCopilotPortalPrompt = (config = defaultAgentConfig()) => {
|
|
4250
|
-
|
|
4251
|
-
return
|
|
4252
|
-
|
|
4253
|
-
|
|
4566
|
+
void config;
|
|
4567
|
+
return renderCopilotWorkflowPrompt(
|
|
4568
|
+
"truthmark-portal",
|
|
4569
|
+
".github/skills/truthmark-portal"
|
|
4254
4570
|
);
|
|
4255
4571
|
};
|
|
4256
4572
|
|
|
@@ -4678,8 +4994,8 @@ var geminiFiles = (config, block) => {
|
|
|
4678
4994
|
return files;
|
|
4679
4995
|
};
|
|
4680
4996
|
var instructionBlockFiles = (paths, block) => {
|
|
4681
|
-
return paths.map((
|
|
4682
|
-
path:
|
|
4997
|
+
return paths.map((path14) => ({
|
|
4998
|
+
path: path14,
|
|
4683
4999
|
content: block,
|
|
4684
5000
|
managedBlock: true
|
|
4685
5001
|
}));
|
|
@@ -4803,16 +5119,16 @@ var upsertManagedBlock = (existingContent, block) => {
|
|
|
4803
5119
|
|
|
4804
5120
|
${block}`;
|
|
4805
5121
|
};
|
|
4806
|
-
var writeManagedAgentsFile = async (rootDir,
|
|
5122
|
+
var writeManagedAgentsFile = async (rootDir, path14 = "AGENTS.md", block) => {
|
|
4807
5123
|
let existingContent = null;
|
|
4808
5124
|
try {
|
|
4809
|
-
existingContent = await fs7.readFile(resolveRepoPath(rootDir,
|
|
5125
|
+
existingContent = await fs7.readFile(resolveRepoPath(rootDir, path14), "utf8");
|
|
4810
5126
|
} catch (error) {
|
|
4811
5127
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
4812
5128
|
throw error;
|
|
4813
5129
|
}
|
|
4814
5130
|
}
|
|
4815
|
-
return writeRepoFile(rootDir,
|
|
5131
|
+
return writeRepoFile(rootDir, path14, upsertManagedBlock(existingContent, block));
|
|
4816
5132
|
};
|
|
4817
5133
|
var diagnosticCategoryForPath = (filePath, config) => {
|
|
4818
5134
|
if (filePath === "AGENTS.md") {
|
|
@@ -5117,6 +5433,11 @@ var parseMarkdownDocument = (source) => {
|
|
|
5117
5433
|
|
|
5118
5434
|
// src/checks/frontmatter.ts
|
|
5119
5435
|
var isTruthDocumentKind2 = (value) => TRUTH_DOCUMENT_KINDS.includes(value);
|
|
5436
|
+
var ROUTE_RELATIONSHIP_FRONTMATTER_FIELDS = [
|
|
5437
|
+
"realized_by",
|
|
5438
|
+
"realizes",
|
|
5439
|
+
"depends_on"
|
|
5440
|
+
];
|
|
5120
5441
|
var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntries = []) => {
|
|
5121
5442
|
const diagnostics = [];
|
|
5122
5443
|
const truthDocumentMap = new Map(
|
|
@@ -5163,6 +5484,28 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
|
|
|
5163
5484
|
}
|
|
5164
5485
|
const routedTruthDocument = truthDocumentMap.get(markdownPath);
|
|
5165
5486
|
const truthKind = document.frontmatter.truth_kind;
|
|
5487
|
+
const truthLane = document.frontmatter.truth_lane;
|
|
5488
|
+
const isTruthDocument = routedTruthDocument !== void 0 || truthKind !== void 0;
|
|
5489
|
+
if (isTruthDocument) {
|
|
5490
|
+
for (const field of ROUTE_RELATIONSHIP_FRONTMATTER_FIELDS) {
|
|
5491
|
+
if (field in document.frontmatter) {
|
|
5492
|
+
diagnostics.push({
|
|
5493
|
+
category: "frontmatter",
|
|
5494
|
+
severity: "error",
|
|
5495
|
+
message: `Frontmatter field ${field} is not allowed on truth documents; author relationship metadata in fenced route YAML entries instead.`,
|
|
5496
|
+
file: markdownPath
|
|
5497
|
+
});
|
|
5498
|
+
}
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5501
|
+
if (truthLane !== void 0 && truthLane !== "product" && truthLane !== "engineering") {
|
|
5502
|
+
diagnostics.push({
|
|
5503
|
+
category: "frontmatter",
|
|
5504
|
+
severity: "error",
|
|
5505
|
+
message: "Frontmatter truth_lane must be product or engineering.",
|
|
5506
|
+
file: markdownPath
|
|
5507
|
+
});
|
|
5508
|
+
}
|
|
5166
5509
|
if (truthKind !== void 0) {
|
|
5167
5510
|
if (typeof truthKind !== "string" || !isTruthDocumentKind2(truthKind)) {
|
|
5168
5511
|
diagnostics.push({
|
|
@@ -5181,6 +5524,14 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
|
|
|
5181
5524
|
file: markdownPath
|
|
5182
5525
|
});
|
|
5183
5526
|
}
|
|
5527
|
+
if (typeof truthKind === "string" && isTruthDocumentKind2(truthKind) && truthLane !== void 0 && (truthLane === "product" || truthLane === "engineering") && truthLane !== laneForTruthDocumentKind(truthKind)) {
|
|
5528
|
+
diagnostics.push({
|
|
5529
|
+
category: "frontmatter",
|
|
5530
|
+
severity: "error",
|
|
5531
|
+
message: `Frontmatter truth_lane ${truthLane} must match truth_kind ${truthKind}.`,
|
|
5532
|
+
file: markdownPath
|
|
5533
|
+
});
|
|
5534
|
+
}
|
|
5184
5535
|
}
|
|
5185
5536
|
}
|
|
5186
5537
|
return diagnostics;
|
|
@@ -5340,7 +5691,9 @@ var resolveAreaRouting = async (rootDir, config) => {
|
|
|
5340
5691
|
};
|
|
5341
5692
|
}
|
|
5342
5693
|
const rootParsed = parseAreasMarkdown(rootRead.source ?? "", {
|
|
5343
|
-
truthDocsRoot: config.truthDocsRoot
|
|
5694
|
+
truthDocsRoot: config.truthDocsRoot,
|
|
5695
|
+
productTruthRoot: config.productTruthRoot,
|
|
5696
|
+
engineeringTruthRoot: config.engineeringTruthRoot
|
|
5344
5697
|
});
|
|
5345
5698
|
diagnostics.push(
|
|
5346
5699
|
...rootParsed.diagnostics.map((diagnostic) => ({
|
|
@@ -5379,7 +5732,9 @@ var resolveAreaRouting = async (rootDir, config) => {
|
|
|
5379
5732
|
}
|
|
5380
5733
|
routeFiles.push(areaFile);
|
|
5381
5734
|
const childParsed = parseAreasMarkdown(childRead.source ?? "", {
|
|
5382
|
-
truthDocsRoot: config.truthDocsRoot
|
|
5735
|
+
truthDocsRoot: config.truthDocsRoot,
|
|
5736
|
+
productTruthRoot: config.productTruthRoot,
|
|
5737
|
+
engineeringTruthRoot: config.engineeringTruthRoot
|
|
5383
5738
|
});
|
|
5384
5739
|
diagnostics.push(
|
|
5385
5740
|
...childParsed.diagnostics.map((diagnostic) => ({
|
|
@@ -5656,11 +6011,115 @@ var BROAD_CODE_SURFACES = /* @__PURE__ */ new Set([
|
|
|
5656
6011
|
var isBroadCodeSurface = (pattern) => {
|
|
5657
6012
|
return BROAD_CODE_SURFACES.has(pattern.replace(/\/\*\*\/\*$/u, "/**"));
|
|
5658
6013
|
};
|
|
6014
|
+
var PRODUCT_LINK_REVIEW_KINDS = /* @__PURE__ */ new Set([
|
|
6015
|
+
"engineering-behavior",
|
|
6016
|
+
"engineering-workflow",
|
|
6017
|
+
"engineering-contract"
|
|
6018
|
+
]);
|
|
6019
|
+
var normalizeRoot = (value) => value.replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
6020
|
+
var validateLaneShape = (entry, config, areaName) => {
|
|
6021
|
+
const diagnostics = [];
|
|
6022
|
+
const productRoot = normalizeRoot(resolveProductTruthRoot(config));
|
|
6023
|
+
const engineeringRoot = normalizeRoot(resolveEngineeringTruthRoot(config));
|
|
6024
|
+
const normalizedPath = entry.path.replaceAll("\\", "/");
|
|
6025
|
+
const expectedLane = laneForTruthDocumentKind(entry.kind);
|
|
6026
|
+
if (entry.lane !== expectedLane) {
|
|
6027
|
+
diagnostics.push({
|
|
6028
|
+
category: "lane-shape",
|
|
6029
|
+
severity: "error",
|
|
6030
|
+
message: `Truth document ${entry.path} declares ${entry.kind} in ${entry.lane} lane; ${entry.kind} belongs to the ${expectedLane} lane.`,
|
|
6031
|
+
area: areaName,
|
|
6032
|
+
file: entry.path
|
|
6033
|
+
});
|
|
6034
|
+
}
|
|
6035
|
+
if (entry.lane === "product" && !normalizedPath.startsWith(`${productRoot}/`)) {
|
|
6036
|
+
diagnostics.push({
|
|
6037
|
+
category: "lane-shape",
|
|
6038
|
+
severity: "error",
|
|
6039
|
+
message: `Product truth document ${entry.path} must live under ${productRoot}.`,
|
|
6040
|
+
area: areaName,
|
|
6041
|
+
file: entry.path
|
|
6042
|
+
});
|
|
6043
|
+
}
|
|
6044
|
+
if (entry.lane === "engineering" && !normalizedPath.startsWith(`${engineeringRoot}/`)) {
|
|
6045
|
+
diagnostics.push({
|
|
6046
|
+
category: "lane-shape",
|
|
6047
|
+
severity: "error",
|
|
6048
|
+
message: `Engineering truth document ${entry.path} must live under ${engineeringRoot}.`,
|
|
6049
|
+
area: areaName,
|
|
6050
|
+
file: entry.path
|
|
6051
|
+
});
|
|
6052
|
+
}
|
|
6053
|
+
return diagnostics;
|
|
6054
|
+
};
|
|
6055
|
+
var validateRelationshipTargets = (entries) => {
|
|
6056
|
+
const diagnostics = [];
|
|
6057
|
+
const byPath = new Map(entries.map((entry) => [entry.path, entry]));
|
|
6058
|
+
for (const entry of entries) {
|
|
6059
|
+
for (const target of entry.realizedBy) {
|
|
6060
|
+
const targetEntry = byPath.get(target);
|
|
6061
|
+
if (!targetEntry) {
|
|
6062
|
+
diagnostics.push({
|
|
6063
|
+
category: "traceability",
|
|
6064
|
+
severity: "error",
|
|
6065
|
+
message: `Product truth document ${entry.path} declares missing engineering realization ${target}.`,
|
|
6066
|
+
file: entry.path
|
|
6067
|
+
});
|
|
6068
|
+
} else if (targetEntry.lane !== "engineering") {
|
|
6069
|
+
diagnostics.push({
|
|
6070
|
+
category: "traceability",
|
|
6071
|
+
severity: "error",
|
|
6072
|
+
message: `Product truth document ${entry.path} realized_by target ${target} must point to engineering truth.`,
|
|
6073
|
+
file: entry.path
|
|
6074
|
+
});
|
|
6075
|
+
}
|
|
6076
|
+
}
|
|
6077
|
+
for (const target of entry.realizes) {
|
|
6078
|
+
const targetEntry = byPath.get(target);
|
|
6079
|
+
if (!targetEntry) {
|
|
6080
|
+
diagnostics.push({
|
|
6081
|
+
category: "traceability",
|
|
6082
|
+
severity: "error",
|
|
6083
|
+
message: `Engineering truth document ${entry.path} declares missing product truth ${target}.`,
|
|
6084
|
+
file: entry.path
|
|
6085
|
+
});
|
|
6086
|
+
} else if (targetEntry.lane !== "product") {
|
|
6087
|
+
diagnostics.push({
|
|
6088
|
+
category: "traceability",
|
|
6089
|
+
severity: "error",
|
|
6090
|
+
message: `Engineering truth document ${entry.path} realizes target ${target} must point to product truth.`,
|
|
6091
|
+
file: entry.path
|
|
6092
|
+
});
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
return diagnostics;
|
|
6097
|
+
};
|
|
6098
|
+
var validateMissingProductLinkReviews = (entries, reportedPaths) => {
|
|
6099
|
+
const diagnostics = [];
|
|
6100
|
+
const hasProductTruth = entries.some((entry) => entry.lane === "product");
|
|
6101
|
+
if (!hasProductTruth) {
|
|
6102
|
+
return diagnostics;
|
|
6103
|
+
}
|
|
6104
|
+
for (const entry of entries) {
|
|
6105
|
+
if (entry.lane === "engineering" && PRODUCT_LINK_REVIEW_KINDS.has(entry.kind) && entry.realizes.length === 0 && !reportedPaths.has(entry.path)) {
|
|
6106
|
+
reportedPaths.add(entry.path);
|
|
6107
|
+
diagnostics.push({
|
|
6108
|
+
category: "traceability",
|
|
6109
|
+
severity: "review",
|
|
6110
|
+
message: `User-visible engineering truth document ${entry.path} should link product truth with realizes when it implements a product capability.`,
|
|
6111
|
+
file: entry.path
|
|
6112
|
+
});
|
|
6113
|
+
}
|
|
6114
|
+
}
|
|
6115
|
+
return diagnostics;
|
|
6116
|
+
};
|
|
5659
6117
|
var checkAreas = async (rootDir, config) => {
|
|
5660
6118
|
const routing = await resolveAreaRouting(rootDir, {
|
|
5661
6119
|
rootIndex: config.truthmark.paths.routesIndex,
|
|
5662
6120
|
areaFilesRoot: config.truthmark.paths.routeAreasRoot,
|
|
5663
|
-
|
|
6121
|
+
productTruthRoot: resolveProductTruthRoot(config),
|
|
6122
|
+
engineeringTruthRoot: resolveEngineeringTruthRoot(config)
|
|
5664
6123
|
});
|
|
5665
6124
|
const discoveredCodeFiles = await fg4([...COVERAGE_SCAN_PATTERNS], {
|
|
5666
6125
|
cwd: rootDir,
|
|
@@ -5676,6 +6135,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5676
6135
|
const truthDocumentPaths = [];
|
|
5677
6136
|
const seenTruthDocumentPaths = /* @__PURE__ */ new Set();
|
|
5678
6137
|
const truthDocumentEntryMap = /* @__PURE__ */ new Map();
|
|
6138
|
+
const reportedMissingProductLinkPaths = /* @__PURE__ */ new Set();
|
|
5679
6139
|
const areaCoverage = routing.areas.map((area) => ({
|
|
5680
6140
|
area,
|
|
5681
6141
|
valid: true,
|
|
@@ -5693,6 +6153,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5693
6153
|
const truthReferences = routing.truthDocumentReferences;
|
|
5694
6154
|
for (const area of truthReferences) {
|
|
5695
6155
|
let areaHasTruthDocumentErrors = false;
|
|
6156
|
+
const areaTruthDocumentEntries = [];
|
|
5696
6157
|
const registerTruthDocumentEntry = (truthDocumentEntry) => {
|
|
5697
6158
|
const existingEntry = truthDocumentEntryMap.get(truthDocumentEntry.path);
|
|
5698
6159
|
if (existingEntry && existingEntry.kind !== truthDocumentEntry.kind) {
|
|
@@ -5705,16 +6166,39 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5705
6166
|
});
|
|
5706
6167
|
return false;
|
|
5707
6168
|
}
|
|
6169
|
+
if (existingEntry && existingEntry.lane !== truthDocumentEntry.lane) {
|
|
6170
|
+
diagnostics.push({
|
|
6171
|
+
category: "area-index",
|
|
6172
|
+
severity: "error",
|
|
6173
|
+
message: `Truth document ${truthDocumentEntry.path} is routed with conflicting lanes ${existingEntry.lane} and ${truthDocumentEntry.lane}.`,
|
|
6174
|
+
area: area.name,
|
|
6175
|
+
file: truthDocumentEntry.path
|
|
6176
|
+
});
|
|
6177
|
+
return false;
|
|
6178
|
+
}
|
|
5708
6179
|
if (!existingEntry) {
|
|
5709
6180
|
truthDocumentEntryMap.set(truthDocumentEntry.path, truthDocumentEntry);
|
|
6181
|
+
} else {
|
|
6182
|
+
truthDocumentEntryMap.set(
|
|
6183
|
+
truthDocumentEntry.path,
|
|
6184
|
+
mergeTruthDocumentEntryRelationships(
|
|
6185
|
+
existingEntry,
|
|
6186
|
+
truthDocumentEntry
|
|
6187
|
+
)
|
|
6188
|
+
);
|
|
5710
6189
|
}
|
|
6190
|
+
areaTruthDocumentEntries.push(truthDocumentEntry);
|
|
6191
|
+
diagnostics.push(
|
|
6192
|
+
...validateLaneShape(truthDocumentEntry, config, area.name)
|
|
6193
|
+
);
|
|
5711
6194
|
return true;
|
|
5712
6195
|
};
|
|
5713
|
-
for (const
|
|
6196
|
+
for (const [
|
|
6197
|
+
truthDocumentIndex,
|
|
6198
|
+
truthDocument
|
|
6199
|
+
] of area.truthDocuments.entries()) {
|
|
6200
|
+
const routedEntry = area.truthDocumentEntries[truthDocumentIndex];
|
|
5714
6201
|
if (looksLikeGlob2(truthDocument)) {
|
|
5715
|
-
const routedGlobEntry = area.truthDocumentEntries.find(
|
|
5716
|
-
(entry) => entry.path === truthDocument
|
|
5717
|
-
);
|
|
5718
6202
|
const matches = (await fg4([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
|
|
5719
6203
|
if (matches.length === 0) {
|
|
5720
6204
|
diagnostics.push({
|
|
@@ -5746,8 +6230,8 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5746
6230
|
seenTruthDocumentPaths.add(match);
|
|
5747
6231
|
truthDocumentPaths.push(match);
|
|
5748
6232
|
}
|
|
5749
|
-
if (
|
|
5750
|
-
...
|
|
6233
|
+
if (routedEntry && !registerTruthDocumentEntry({
|
|
6234
|
+
...routedEntry,
|
|
5751
6235
|
path: match
|
|
5752
6236
|
})) {
|
|
5753
6237
|
areaHasTruthDocumentErrors = true;
|
|
@@ -5785,7 +6269,6 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5785
6269
|
seenTruthDocumentPaths.add(truthDocument);
|
|
5786
6270
|
truthDocumentPaths.push(truthDocument);
|
|
5787
6271
|
}
|
|
5788
|
-
const routedEntry = area.truthDocumentEntries.find((entry) => entry.path === truthDocument);
|
|
5789
6272
|
if (routedEntry && !registerTruthDocumentEntry(routedEntry)) {
|
|
5790
6273
|
areaHasTruthDocumentErrors = true;
|
|
5791
6274
|
}
|
|
@@ -5800,6 +6283,12 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5800
6283
|
matchingArea.valid = false;
|
|
5801
6284
|
}
|
|
5802
6285
|
}
|
|
6286
|
+
diagnostics.push(
|
|
6287
|
+
...validateMissingProductLinkReviews(
|
|
6288
|
+
areaTruthDocumentEntries,
|
|
6289
|
+
reportedMissingProductLinkPaths
|
|
6290
|
+
)
|
|
6291
|
+
);
|
|
5803
6292
|
}
|
|
5804
6293
|
for (const entry of areaCoverage) {
|
|
5805
6294
|
const { area } = entry;
|
|
@@ -5826,7 +6315,10 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5826
6315
|
let containedMatches = 0;
|
|
5827
6316
|
for (const match of matches) {
|
|
5828
6317
|
try {
|
|
5829
|
-
await assertRepoContainment(
|
|
6318
|
+
await assertRepoContainment(
|
|
6319
|
+
rootDir,
|
|
6320
|
+
resolveRepoPath(rootDir, match)
|
|
6321
|
+
);
|
|
5830
6322
|
containedMatches += 1;
|
|
5831
6323
|
} catch {
|
|
5832
6324
|
diagnostics.push({
|
|
@@ -5898,10 +6390,12 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5898
6390
|
const topologyPressureCount = broadAreaCount + diagnostics.filter(
|
|
5899
6391
|
(diagnostic) => diagnostic.category === "area-index" && diagnostic.severity === "review"
|
|
5900
6392
|
).length;
|
|
6393
|
+
const truthDocumentEntries = [...truthDocumentEntryMap.values()];
|
|
6394
|
+
diagnostics.push(...validateRelationshipTargets(truthDocumentEntries));
|
|
5901
6395
|
return {
|
|
5902
6396
|
diagnostics,
|
|
5903
6397
|
truthDocumentPaths,
|
|
5904
|
-
truthDocumentEntries
|
|
6398
|
+
truthDocumentEntries,
|
|
5905
6399
|
routePrecision: {
|
|
5906
6400
|
leafAreaCount: routing.areas.length,
|
|
5907
6401
|
broadAreaCount
|
|
@@ -5913,7 +6407,38 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5913
6407
|
// src/checks/decisions.ts
|
|
5914
6408
|
import fs14 from "fs/promises";
|
|
5915
6409
|
import micromatch5 from "micromatch";
|
|
5916
|
-
var
|
|
6410
|
+
var PRODUCT_CAPABILITY_REQUIRED_HEADINGS = [
|
|
6411
|
+
"Capability Promise",
|
|
6412
|
+
"Users And Value",
|
|
6413
|
+
"Capability Scope",
|
|
6414
|
+
"Current Product Behavior",
|
|
6415
|
+
"Acceptance Criteria",
|
|
6416
|
+
"Product Decisions",
|
|
6417
|
+
"Engineering Realization Links",
|
|
6418
|
+
"Non-Goals"
|
|
6419
|
+
];
|
|
6420
|
+
var ENGINEERING_REQUIRED_HEADINGS = [
|
|
6421
|
+
"Purpose",
|
|
6422
|
+
"Scope",
|
|
6423
|
+
"Current Implementation Behavior",
|
|
6424
|
+
"Source References",
|
|
6425
|
+
"Product Truth Links",
|
|
6426
|
+
"Maintenance Notes"
|
|
6427
|
+
];
|
|
6428
|
+
var FORBIDDEN_PRODUCT_HEADINGS = [
|
|
6429
|
+
"Execution Flow",
|
|
6430
|
+
"Execution Model",
|
|
6431
|
+
"Generated File Inventory",
|
|
6432
|
+
"CLI Envelope Details",
|
|
6433
|
+
"Data And Control Flow"
|
|
6434
|
+
];
|
|
6435
|
+
var FORBIDDEN_ENGINEERING_HEADINGS = [
|
|
6436
|
+
"Product Promise",
|
|
6437
|
+
"User / Stakeholder Value",
|
|
6438
|
+
"Product Decisions",
|
|
6439
|
+
"Product Rationale",
|
|
6440
|
+
"Business Boundary"
|
|
6441
|
+
];
|
|
5917
6442
|
var isTruthDocumentKind3 = (value) => {
|
|
5918
6443
|
return TRUTH_DOCUMENT_KINDS.includes(value);
|
|
5919
6444
|
};
|
|
@@ -5921,16 +6446,18 @@ var escapeRegExp2 = (value) => {
|
|
|
5921
6446
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5922
6447
|
};
|
|
5923
6448
|
var hasHeading = (source, heading) => {
|
|
5924
|
-
return new RegExp(`^#{2,3}\\s+${escapeRegExp2(heading)}\\s*$`, "mu").test(
|
|
6449
|
+
return new RegExp(`^#{2,3}\\s+${escapeRegExp2(heading)}\\s*$`, "mu").test(
|
|
6450
|
+
source
|
|
6451
|
+
);
|
|
5925
6452
|
};
|
|
5926
6453
|
var kindSpecificHeadingMessages = (source, kind) => {
|
|
5927
6454
|
if (kind === null) {
|
|
5928
6455
|
return [];
|
|
5929
6456
|
}
|
|
5930
|
-
if (kind === "behavior") {
|
|
5931
|
-
return hasHeading(source, "Current Behavior") ? [] : ["Current Behavior"];
|
|
6457
|
+
if (kind === "engineering-behavior") {
|
|
6458
|
+
return hasHeading(source, "Current Implementation Behavior") ? [] : ["Current Implementation Behavior"];
|
|
5932
6459
|
}
|
|
5933
|
-
if (kind === "contract") {
|
|
6460
|
+
if (kind === "engineering-contract") {
|
|
5934
6461
|
const missingMessages = [];
|
|
5935
6462
|
if (!hasHeading(source, "Contract Surface")) {
|
|
5936
6463
|
missingMessages.push("Contract Surface");
|
|
@@ -5940,10 +6467,10 @@ var kindSpecificHeadingMessages = (source, kind) => {
|
|
|
5940
6467
|
}
|
|
5941
6468
|
return missingMessages;
|
|
5942
6469
|
}
|
|
5943
|
-
if (kind === "architecture") {
|
|
6470
|
+
if (kind === "engineering-architecture") {
|
|
5944
6471
|
return hasHeading(source, "Boundaries") || hasHeading(source, "Components") ? [] : ["Boundaries or Components"];
|
|
5945
6472
|
}
|
|
5946
|
-
if (kind === "workflow") {
|
|
6473
|
+
if (kind === "engineering-workflow") {
|
|
5947
6474
|
const missingMessages = [];
|
|
5948
6475
|
if (!hasHeading(source, "Triggers")) {
|
|
5949
6476
|
missingMessages.push("Triggers");
|
|
@@ -5953,23 +6480,34 @@ var kindSpecificHeadingMessages = (source, kind) => {
|
|
|
5953
6480
|
}
|
|
5954
6481
|
return missingMessages;
|
|
5955
6482
|
}
|
|
5956
|
-
if (kind === "operations") {
|
|
6483
|
+
if (kind === "engineering-operations") {
|
|
5957
6484
|
return hasHeading(source, "Runtime Topology") || hasHeading(source, "Configuration") ? [] : ["Runtime Topology or Configuration"];
|
|
5958
6485
|
}
|
|
5959
|
-
if (kind === "test-behavior") {
|
|
6486
|
+
if (kind === "engineering-test-behavior") {
|
|
5960
6487
|
const missingMessages = [];
|
|
5961
6488
|
if (!hasHeading(source, "Execution Model")) {
|
|
5962
6489
|
missingMessages.push("Execution Model");
|
|
5963
6490
|
}
|
|
5964
6491
|
if (!hasHeading(source, "Fixtures And Data Model") && !hasHeading(source, "Assertions And Invariants")) {
|
|
5965
|
-
missingMessages.push(
|
|
6492
|
+
missingMessages.push(
|
|
6493
|
+
"Fixtures And Data Model or Assertions And Invariants"
|
|
6494
|
+
);
|
|
5966
6495
|
}
|
|
5967
6496
|
return missingMessages;
|
|
5968
6497
|
}
|
|
5969
6498
|
return [];
|
|
5970
6499
|
};
|
|
6500
|
+
var productRequiredHeadings = (kind) => {
|
|
6501
|
+
if (kind === "product-capability") {
|
|
6502
|
+
return PRODUCT_CAPABILITY_REQUIRED_HEADINGS;
|
|
6503
|
+
}
|
|
6504
|
+
return PRODUCT_CAPABILITY_REQUIRED_HEADINGS;
|
|
6505
|
+
};
|
|
5971
6506
|
var decisionTruthGlobs = (config) => {
|
|
5972
|
-
return [
|
|
6507
|
+
return [
|
|
6508
|
+
`${resolveProductTruthRoot(config)}/**/*.md`,
|
|
6509
|
+
`${resolveEngineeringTruthRoot(config)}/**/*.md`
|
|
6510
|
+
];
|
|
5973
6511
|
};
|
|
5974
6512
|
var isDecisionTruthCandidate = (config, filePath) => {
|
|
5975
6513
|
return !filePath.endsWith("/README.md") && micromatch5.isMatch(filePath, decisionTruthGlobs(config));
|
|
@@ -5983,25 +6521,43 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
|
|
|
5983
6521
|
(filePath) => truthDocumentMap.has(filePath) || isDecisionTruthCandidate(config, filePath)
|
|
5984
6522
|
).sort();
|
|
5985
6523
|
for (const filePath of candidatePaths) {
|
|
5986
|
-
const source = await fs14.readFile(
|
|
6524
|
+
const source = await fs14.readFile(
|
|
6525
|
+
resolveRepoPath(rootDir, filePath),
|
|
6526
|
+
"utf8"
|
|
6527
|
+
);
|
|
5987
6528
|
const document = parseMarkdownDocument(source);
|
|
5988
6529
|
const routedTruthDocument = truthDocumentMap.get(filePath);
|
|
5989
6530
|
const frontmatterTruthKind = typeof document.frontmatter.truth_kind === "string" ? document.frontmatter.truth_kind : null;
|
|
5990
6531
|
const routedTruthKind = routedTruthDocument?.kindSource === "defaulted" ? null : routedTruthDocument?.kind;
|
|
5991
6532
|
const truthKind = routedTruthKind ?? (frontmatterTruthKind && isTruthDocumentKind3(frontmatterTruthKind) ? frontmatterTruthKind : inferTruthDocumentKindFromPath(filePath));
|
|
5992
|
-
const
|
|
6533
|
+
const lane = routedTruthDocument?.lane ?? (truthKind ? laneForTruthDocumentKind(truthKind) : filePath.startsWith(resolveProductTruthRoot(config)) ? "product" : "engineering");
|
|
6534
|
+
const requiredHeadings = lane === "product" ? productRequiredHeadings(truthKind) : ENGINEERING_REQUIRED_HEADINGS;
|
|
6535
|
+
const missingHeadings = requiredHeadings.filter(
|
|
5993
6536
|
(heading) => !hasHeading(source, heading)
|
|
5994
6537
|
);
|
|
5995
|
-
|
|
5996
|
-
|
|
6538
|
+
if (lane === "engineering") {
|
|
6539
|
+
missingHeadings.push(...kindSpecificHeadingMessages(source, truthKind));
|
|
6540
|
+
}
|
|
6541
|
+
const forbiddenHeadings = (lane === "product" ? FORBIDDEN_PRODUCT_HEADINGS : FORBIDDEN_ENGINEERING_HEADINGS).filter((heading) => hasHeading(source, heading));
|
|
6542
|
+
if (missingHeadings.length === 0 && forbiddenHeadings.length === 0) {
|
|
5997
6543
|
continue;
|
|
5998
6544
|
}
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6545
|
+
if (missingHeadings.length > 0) {
|
|
6546
|
+
diagnostics.push({
|
|
6547
|
+
category: "doc-structure",
|
|
6548
|
+
severity: "review",
|
|
6549
|
+
message: `Canonical ${lane} truth doc ${filePath} should include ${missingHeadings.join(" and ")} section(s). Product truth says what must be true and why; engineering truth says how the repository currently realizes it.`,
|
|
6550
|
+
file: filePath
|
|
6551
|
+
});
|
|
6552
|
+
}
|
|
6553
|
+
if (forbiddenHeadings.length > 0) {
|
|
6554
|
+
diagnostics.push({
|
|
6555
|
+
category: "lane-drift",
|
|
6556
|
+
severity: "error",
|
|
6557
|
+
message: `Canonical ${lane} truth doc ${filePath} contains wrong-lane section(s): ${forbiddenHeadings.join(", ")}.`,
|
|
6558
|
+
file: filePath
|
|
6559
|
+
});
|
|
6560
|
+
}
|
|
6005
6561
|
}
|
|
6006
6562
|
return diagnostics;
|
|
6007
6563
|
};
|
|
@@ -6090,20 +6646,99 @@ var checkGeneratedSurfaces = async (rootDir, config) => {
|
|
|
6090
6646
|
};
|
|
6091
6647
|
|
|
6092
6648
|
// src/impact/build.ts
|
|
6093
|
-
import
|
|
6649
|
+
import path11 from "path";
|
|
6094
6650
|
import micromatch7 from "micromatch";
|
|
6095
6651
|
|
|
6096
6652
|
// src/repo-index/build.ts
|
|
6097
6653
|
import fs18 from "fs/promises";
|
|
6098
|
-
import
|
|
6654
|
+
import path9 from "path";
|
|
6099
6655
|
|
|
6100
6656
|
// src/repo-index/file-tree.ts
|
|
6101
6657
|
import fs16 from "fs/promises";
|
|
6102
|
-
import
|
|
6658
|
+
import path7 from "path";
|
|
6103
6659
|
import { execa as execa2 } from "execa";
|
|
6104
6660
|
import fg5 from "fast-glob";
|
|
6105
|
-
import
|
|
6661
|
+
import matter3 from "gray-matter";
|
|
6106
6662
|
import micromatch6 from "micromatch";
|
|
6663
|
+
|
|
6664
|
+
// src/truth/source-references.ts
|
|
6665
|
+
import path6 from "path";
|
|
6666
|
+
import matter2 from "gray-matter";
|
|
6667
|
+
var repoRootPrefixes = [
|
|
6668
|
+
".codex/",
|
|
6669
|
+
".github/",
|
|
6670
|
+
".truthmark/",
|
|
6671
|
+
"docs/",
|
|
6672
|
+
"src/",
|
|
6673
|
+
"tests/"
|
|
6674
|
+
];
|
|
6675
|
+
var sourceReferencesHeadingPattern = /^##\s+Source References\s*$/imu;
|
|
6676
|
+
var nextSecondLevelHeadingPattern = /^##\s+/imu;
|
|
6677
|
+
var bulletReferencePattern = /^\s*[-*]\s+(.+?)\s*$/u;
|
|
6678
|
+
var markdownLinkPattern = /^\s*\[[^\]]+\]\(([^)]+)\)\s*$/u;
|
|
6679
|
+
var backtickPathPattern = /^\s*`([^`]+)`\s*$/u;
|
|
6680
|
+
var normalizeSourceReferencePath = (truthDocPath, referencePath) => {
|
|
6681
|
+
const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
|
|
6682
|
+
const isRepoRelative = repoRootPrefixes.some(
|
|
6683
|
+
(prefix) => strippedPath.startsWith(prefix)
|
|
6684
|
+
);
|
|
6685
|
+
if (!isRepoRelative && (strippedPath.startsWith(".") || !strippedPath.includes("/"))) {
|
|
6686
|
+
return path6.posix.normalize(
|
|
6687
|
+
path6.posix.join(path6.posix.dirname(truthDocPath), strippedPath)
|
|
6688
|
+
);
|
|
6689
|
+
}
|
|
6690
|
+
return path6.posix.normalize(strippedPath);
|
|
6691
|
+
};
|
|
6692
|
+
var normalizeReferenceText = (value) => {
|
|
6693
|
+
const trimmed = value.trim();
|
|
6694
|
+
const markdownLinkMatch = markdownLinkPattern.exec(trimmed);
|
|
6695
|
+
if (markdownLinkMatch) {
|
|
6696
|
+
return markdownLinkMatch[1]?.trim() ?? "";
|
|
6697
|
+
}
|
|
6698
|
+
const backtickMatch = backtickPathPattern.exec(trimmed);
|
|
6699
|
+
if (backtickMatch) {
|
|
6700
|
+
return backtickMatch[1]?.trim() ?? "";
|
|
6701
|
+
}
|
|
6702
|
+
return trimmed;
|
|
6703
|
+
};
|
|
6704
|
+
var parseSourceReferencesSection = (content) => {
|
|
6705
|
+
const headingMatch = sourceReferencesHeadingPattern.exec(content);
|
|
6706
|
+
if (!headingMatch || typeof headingMatch.index !== "number") {
|
|
6707
|
+
return [];
|
|
6708
|
+
}
|
|
6709
|
+
const sectionStart = headingMatch.index + headingMatch[0].length;
|
|
6710
|
+
const afterHeading = content.slice(sectionStart);
|
|
6711
|
+
const nextHeadingMatch = nextSecondLevelHeadingPattern.exec(afterHeading);
|
|
6712
|
+
const section = nextHeadingMatch ? afterHeading.slice(0, nextHeadingMatch.index) : afterHeading;
|
|
6713
|
+
const references = [];
|
|
6714
|
+
for (const line of section.split("\n")) {
|
|
6715
|
+
const bulletMatch = bulletReferencePattern.exec(line);
|
|
6716
|
+
if (!bulletMatch) {
|
|
6717
|
+
continue;
|
|
6718
|
+
}
|
|
6719
|
+
const reference = normalizeReferenceText(bulletMatch[1] ?? "");
|
|
6720
|
+
if (reference.length > 0 && !reference.startsWith("{{")) {
|
|
6721
|
+
references.push(reference);
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6724
|
+
return references;
|
|
6725
|
+
};
|
|
6726
|
+
var parseSourceReferences = (source, truthDocPath) => {
|
|
6727
|
+
const parsed = matter2(source);
|
|
6728
|
+
const references = /* @__PURE__ */ new Set();
|
|
6729
|
+
const frontmatterSourceOfTruth = Array.isArray(parsed.data.source_of_truth) ? parsed.data.source_of_truth : [];
|
|
6730
|
+
for (const entry of frontmatterSourceOfTruth) {
|
|
6731
|
+
if (typeof entry === "string") {
|
|
6732
|
+
references.add(normalizeSourceReferencePath(truthDocPath, entry));
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
for (const entry of parseSourceReferencesSection(parsed.content)) {
|
|
6736
|
+
references.add(normalizeSourceReferencePath(truthDocPath, entry));
|
|
6737
|
+
}
|
|
6738
|
+
return [...references].sort();
|
|
6739
|
+
};
|
|
6740
|
+
|
|
6741
|
+
// src/repo-index/file-tree.ts
|
|
6107
6742
|
var languageByExtension = /* @__PURE__ */ new Map([
|
|
6108
6743
|
[".ts", "typescript"],
|
|
6109
6744
|
[".tsx", "typescript"],
|
|
@@ -6117,12 +6752,19 @@ var languageByExtension = /* @__PURE__ */ new Map([
|
|
|
6117
6752
|
[".yaml", "yaml"],
|
|
6118
6753
|
[".toml", "toml"]
|
|
6119
6754
|
]);
|
|
6120
|
-
var sourceExtensions = /* @__PURE__ */ new Set([
|
|
6755
|
+
var sourceExtensions = /* @__PURE__ */ new Set([
|
|
6756
|
+
".ts",
|
|
6757
|
+
".tsx",
|
|
6758
|
+
".js",
|
|
6759
|
+
".jsx",
|
|
6760
|
+
".mjs",
|
|
6761
|
+
".cjs"
|
|
6762
|
+
]);
|
|
6121
6763
|
var isJavaScriptLikePath = (filePath) => {
|
|
6122
|
-
return sourceExtensions.has(
|
|
6764
|
+
return sourceExtensions.has(path7.posix.extname(filePath));
|
|
6123
6765
|
};
|
|
6124
6766
|
var isTestPath = (filePath) => {
|
|
6125
|
-
return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(
|
|
6767
|
+
return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path7.posix.basename(filePath));
|
|
6126
6768
|
};
|
|
6127
6769
|
var fileKind = (filePath, ignore) => {
|
|
6128
6770
|
const classification = classifyPath(filePath, ignore);
|
|
@@ -6148,12 +6790,14 @@ var fileKind = (filePath, ignore) => {
|
|
|
6148
6790
|
};
|
|
6149
6791
|
var targetHintsForTest = (filePath) => {
|
|
6150
6792
|
const hints = /* @__PURE__ */ new Set();
|
|
6151
|
-
const basename =
|
|
6793
|
+
const basename = path7.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
|
|
6152
6794
|
if (basename.length > 0) {
|
|
6153
6795
|
hints.add(basename);
|
|
6154
6796
|
}
|
|
6155
6797
|
const segments = filePath.split("/");
|
|
6156
|
-
const testRootIndex = segments.findIndex(
|
|
6798
|
+
const testRootIndex = segments.findIndex(
|
|
6799
|
+
(segment) => segment === "tests" || segment === "__tests__"
|
|
6800
|
+
);
|
|
6157
6801
|
if (testRootIndex >= 0) {
|
|
6158
6802
|
for (const segment of segments.slice(testRootIndex + 1, -1)) {
|
|
6159
6803
|
if (segment.length > 0) {
|
|
@@ -6165,6 +6809,9 @@ var targetHintsForTest = (filePath) => {
|
|
|
6165
6809
|
};
|
|
6166
6810
|
var defaultIgnore = [".git/**", "node_modules/**", "dist/**", "build/**"];
|
|
6167
6811
|
var normalizePath2 = (filePath) => filePath.replaceAll("\\", "/").replace(/^\.\/+/u, "");
|
|
6812
|
+
var isTruthDocumentKind4 = (value) => {
|
|
6813
|
+
return typeof value === "string" && TRUTH_DOCUMENT_KINDS.includes(value);
|
|
6814
|
+
};
|
|
6168
6815
|
var gitDiscoverableFiles = async (rootDir) => {
|
|
6169
6816
|
const result = await execa2(
|
|
6170
6817
|
"git",
|
|
@@ -6196,7 +6843,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6196
6843
|
for (const filePath of discoveredFiles.filter((entry) => !isIgnoredPath(entry, ignore)).sort()) {
|
|
6197
6844
|
let stat;
|
|
6198
6845
|
try {
|
|
6199
|
-
stat = await fs16.stat(
|
|
6846
|
+
stat = await fs16.stat(path7.join(rootDir, filePath));
|
|
6200
6847
|
} catch (error) {
|
|
6201
6848
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
6202
6849
|
continue;
|
|
@@ -6206,7 +6853,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6206
6853
|
if (!stat.isFile()) {
|
|
6207
6854
|
continue;
|
|
6208
6855
|
}
|
|
6209
|
-
const extension =
|
|
6856
|
+
const extension = path7.posix.extname(filePath);
|
|
6210
6857
|
const kind = fileKind(filePath, ignore);
|
|
6211
6858
|
files.push({
|
|
6212
6859
|
path: filePath,
|
|
@@ -6220,17 +6867,23 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6220
6867
|
});
|
|
6221
6868
|
}
|
|
6222
6869
|
if (kind === "doc") {
|
|
6223
|
-
const source = await fs16.readFile(
|
|
6224
|
-
const parsed =
|
|
6870
|
+
const source = await fs16.readFile(path7.join(rootDir, filePath), "utf8");
|
|
6871
|
+
const parsed = matter3(source);
|
|
6225
6872
|
const markdown = parseMarkdownDocument(parsed.content);
|
|
6226
6873
|
const title = markdown.headings.find((heading) => heading.depth === 1)?.text ?? null;
|
|
6227
|
-
const sourceOfTruth =
|
|
6874
|
+
const sourceOfTruth = parseSourceReferences(source, filePath);
|
|
6875
|
+
const truthKind = typeof parsed.data.truth_kind === "string" ? parsed.data.truth_kind : null;
|
|
6876
|
+
const derivedTruthKind = isTruthDocumentKind4(truthKind) ? truthKind : null;
|
|
6228
6877
|
docs.push({
|
|
6229
6878
|
path: filePath,
|
|
6230
6879
|
title,
|
|
6231
|
-
docType: typeof parsed.data.doc_type === "string" ? parsed.data.doc_type : null,
|
|
6232
|
-
truthKind
|
|
6233
|
-
|
|
6880
|
+
docType: typeof parsed.data.doc_type === "string" ? parsed.data.doc_type : derivedTruthKind ? docTypeForTruthDocumentKind(derivedTruthKind) : null,
|
|
6881
|
+
truthKind,
|
|
6882
|
+
truthLane: typeof parsed.data.truth_lane === "string" ? parsed.data.truth_lane : derivedTruthKind ? laneForTruthDocumentKind(derivedTruthKind) : null,
|
|
6883
|
+
sourceOfTruth: sourceOfTruth.sort(),
|
|
6884
|
+
realizedBy: [],
|
|
6885
|
+
realizes: [],
|
|
6886
|
+
dependsOn: []
|
|
6234
6887
|
});
|
|
6235
6888
|
}
|
|
6236
6889
|
}
|
|
@@ -6243,7 +6896,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6243
6896
|
|
|
6244
6897
|
// src/repo-index/package-metadata.ts
|
|
6245
6898
|
import fs17 from "fs/promises";
|
|
6246
|
-
import
|
|
6899
|
+
import path8 from "path";
|
|
6247
6900
|
import fg6 from "fast-glob";
|
|
6248
6901
|
var packageManagerFor = async (rootDir, packageDir) => {
|
|
6249
6902
|
const lockfiles = [
|
|
@@ -6255,7 +6908,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
|
|
|
6255
6908
|
];
|
|
6256
6909
|
for (const [lockfile, manager] of lockfiles) {
|
|
6257
6910
|
try {
|
|
6258
|
-
await fs17.access(
|
|
6911
|
+
await fs17.access(path8.join(rootDir, packageDir, lockfile));
|
|
6259
6912
|
return manager;
|
|
6260
6913
|
} catch {
|
|
6261
6914
|
continue;
|
|
@@ -6272,8 +6925,8 @@ var discoverPackageMetadata = async (rootDir) => {
|
|
|
6272
6925
|
});
|
|
6273
6926
|
const packages = [];
|
|
6274
6927
|
for (const packageFile of packageFiles.sort()) {
|
|
6275
|
-
const packageDir =
|
|
6276
|
-
const raw = JSON.parse(await fs17.readFile(
|
|
6928
|
+
const packageDir = path8.posix.dirname(packageFile) === "." ? "" : path8.posix.dirname(packageFile);
|
|
6929
|
+
const raw = JSON.parse(await fs17.readFile(path8.join(rootDir, packageFile), "utf8"));
|
|
6277
6930
|
const scripts = raw.scripts && typeof raw.scripts === "object" ? Object.keys(raw.scripts).sort() : [];
|
|
6278
6931
|
packages.push({
|
|
6279
6932
|
path: packageFile,
|
|
@@ -6288,6 +6941,21 @@ var discoverPackageMetadata = async (rootDir) => {
|
|
|
6288
6941
|
};
|
|
6289
6942
|
|
|
6290
6943
|
// src/repo-index/route-map.ts
|
|
6944
|
+
var mergedEntryMap = (entries) => {
|
|
6945
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6946
|
+
for (const entry of entries) {
|
|
6947
|
+
const existingEntry = byPath.get(entry.path);
|
|
6948
|
+
if (existingEntry && existingEntry.kind === entry.kind && existingEntry.lane === entry.lane) {
|
|
6949
|
+
byPath.set(
|
|
6950
|
+
entry.path,
|
|
6951
|
+
mergeTruthDocumentEntryRelationships(existingEntry, entry)
|
|
6952
|
+
);
|
|
6953
|
+
} else if (!existingEntry) {
|
|
6954
|
+
byPath.set(entry.path, entry);
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6957
|
+
return byPath;
|
|
6958
|
+
};
|
|
6291
6959
|
var buildRouteMap = async (rootDir) => {
|
|
6292
6960
|
const loadResult = await loadConfig(rootDir);
|
|
6293
6961
|
if (!loadResult.config) {
|
|
@@ -6300,8 +6968,12 @@ var buildRouteMap = async (rootDir) => {
|
|
|
6300
6968
|
const routing = await resolveAreaRouting(rootDir, {
|
|
6301
6969
|
rootIndex: loadResult.config.truthmark.paths.routesIndex,
|
|
6302
6970
|
areaFilesRoot: loadResult.config.truthmark.paths.routeAreasRoot,
|
|
6303
|
-
|
|
6971
|
+
productTruthRoot: resolveProductTruthRoot(loadResult.config),
|
|
6972
|
+
engineeringTruthRoot: resolveEngineeringTruthRoot(loadResult.config)
|
|
6304
6973
|
});
|
|
6974
|
+
const mergedTruthDocumentEntries = mergedEntryMap(
|
|
6975
|
+
routing.areas.flatMap((area) => area.truthDocumentEntries)
|
|
6976
|
+
);
|
|
6305
6977
|
return {
|
|
6306
6978
|
schemaVersion: "route-map/v0",
|
|
6307
6979
|
routes: routing.areas.map((area) => ({
|
|
@@ -6312,6 +6984,14 @@ var buildRouteMap = async (rootDir) => {
|
|
|
6312
6984
|
parentName: area.parentName,
|
|
6313
6985
|
codeSurface: [...area.codeSurface].sort(),
|
|
6314
6986
|
truthDocs: [...area.truthDocuments].sort(),
|
|
6987
|
+
truthDocumentEntries: [
|
|
6988
|
+
...new Map(
|
|
6989
|
+
area.truthDocumentEntries.map((entry) => [
|
|
6990
|
+
entry.path,
|
|
6991
|
+
mergedTruthDocumentEntries.get(entry.path) ?? entry
|
|
6992
|
+
])
|
|
6993
|
+
).values()
|
|
6994
|
+
].sort((left, right) => left.path.localeCompare(right.path)),
|
|
6315
6995
|
updateTruthWhen: [...area.updateTruthWhen]
|
|
6316
6996
|
})).sort((left, right) => left.key.localeCompare(right.key)),
|
|
6317
6997
|
diagnostics: routing.diagnostics
|
|
@@ -6332,16 +7012,16 @@ var declarationName = (node) => {
|
|
|
6332
7012
|
}
|
|
6333
7013
|
return node.name.text;
|
|
6334
7014
|
};
|
|
6335
|
-
var addExport = (exports, publicSymbols,
|
|
7015
|
+
var addExport = (exports, publicSymbols, path14, name, kind) => {
|
|
6336
7016
|
if (!name) {
|
|
6337
7017
|
return;
|
|
6338
7018
|
}
|
|
6339
|
-
const entry = { path:
|
|
7019
|
+
const entry = { path: path14, name, kind };
|
|
6340
7020
|
exports.push(entry);
|
|
6341
7021
|
publicSymbols.push(entry);
|
|
6342
7022
|
};
|
|
6343
|
-
var analyzeTypeScriptSource = (
|
|
6344
|
-
const sourceFile = ts.createSourceFile(
|
|
7023
|
+
var analyzeTypeScriptSource = (path14, source) => {
|
|
7024
|
+
const sourceFile = ts.createSourceFile(path14, source, ts.ScriptTarget.Latest, true);
|
|
6345
7025
|
const imports = [];
|
|
6346
7026
|
const exports = [];
|
|
6347
7027
|
const publicSymbols = [];
|
|
@@ -6361,7 +7041,7 @@ var analyzeTypeScriptSource = (path13, source) => {
|
|
|
6361
7041
|
}
|
|
6362
7042
|
}
|
|
6363
7043
|
imports.push({
|
|
6364
|
-
from:
|
|
7044
|
+
from: path14,
|
|
6365
7045
|
specifier: statement.moduleSpecifier.text,
|
|
6366
7046
|
imported: sortStrings(imported)
|
|
6367
7047
|
});
|
|
@@ -6370,34 +7050,34 @@ var analyzeTypeScriptSource = (path13, source) => {
|
|
|
6370
7050
|
if (ts.isExportDeclaration(statement)) {
|
|
6371
7051
|
if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
6372
7052
|
for (const element of statement.exportClause.elements) {
|
|
6373
|
-
addExport(exports, publicSymbols,
|
|
7053
|
+
addExport(exports, publicSymbols, path14, element.name.text, "re-export");
|
|
6374
7054
|
}
|
|
6375
7055
|
}
|
|
6376
7056
|
continue;
|
|
6377
7057
|
}
|
|
6378
7058
|
if (ts.isFunctionDeclaration(statement) && hasExportModifier(statement)) {
|
|
6379
|
-
addExport(exports, publicSymbols,
|
|
7059
|
+
addExport(exports, publicSymbols, path14, declarationName(statement), "function");
|
|
6380
7060
|
continue;
|
|
6381
7061
|
}
|
|
6382
7062
|
if (ts.isClassDeclaration(statement) && hasExportModifier(statement)) {
|
|
6383
|
-
addExport(exports, publicSymbols,
|
|
7063
|
+
addExport(exports, publicSymbols, path14, declarationName(statement), "class");
|
|
6384
7064
|
continue;
|
|
6385
7065
|
}
|
|
6386
7066
|
if (ts.isInterfaceDeclaration(statement) && hasExportModifier(statement)) {
|
|
6387
|
-
addExport(exports, publicSymbols,
|
|
7067
|
+
addExport(exports, publicSymbols, path14, declarationName(statement), "interface");
|
|
6388
7068
|
continue;
|
|
6389
7069
|
}
|
|
6390
7070
|
if (ts.isTypeAliasDeclaration(statement) && hasExportModifier(statement)) {
|
|
6391
|
-
addExport(exports, publicSymbols,
|
|
7071
|
+
addExport(exports, publicSymbols, path14, declarationName(statement), "type");
|
|
6392
7072
|
continue;
|
|
6393
7073
|
}
|
|
6394
7074
|
if (ts.isEnumDeclaration(statement) && hasExportModifier(statement)) {
|
|
6395
|
-
addExport(exports, publicSymbols,
|
|
7075
|
+
addExport(exports, publicSymbols, path14, declarationName(statement), "enum");
|
|
6396
7076
|
continue;
|
|
6397
7077
|
}
|
|
6398
7078
|
if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
|
|
6399
7079
|
for (const declaration of statement.declarationList.declarations) {
|
|
6400
|
-
addExport(exports, publicSymbols,
|
|
7080
|
+
addExport(exports, publicSymbols, path14, declarationName(declaration), "const");
|
|
6401
7081
|
}
|
|
6402
7082
|
}
|
|
6403
7083
|
}
|
|
@@ -6428,7 +7108,7 @@ var buildRepoIndex = async (cwd) => {
|
|
|
6428
7108
|
if (!isJavaScriptLikePath(file.path)) {
|
|
6429
7109
|
continue;
|
|
6430
7110
|
}
|
|
6431
|
-
const source = await fs18.readFile(
|
|
7111
|
+
const source = await fs18.readFile(path9.join(rootDir, file.path), "utf8");
|
|
6432
7112
|
const analysis = analyzeTypeScriptSource(file.path, source);
|
|
6433
7113
|
imports.push(...analysis.imports);
|
|
6434
7114
|
exports.push(...analysis.exports);
|
|
@@ -6460,7 +7140,7 @@ import { execa as execa4 } from "execa";
|
|
|
6460
7140
|
|
|
6461
7141
|
// src/git/changes.ts
|
|
6462
7142
|
import fs19 from "fs/promises";
|
|
6463
|
-
import
|
|
7143
|
+
import path10 from "path";
|
|
6464
7144
|
import { execa as execa3 } from "execa";
|
|
6465
7145
|
var normalizePath3 = (filePath) => {
|
|
6466
7146
|
return filePath.replaceAll("\\", "/").replace(/^\.\//u, "");
|
|
@@ -6515,7 +7195,7 @@ var getUncommittedChanges = async (cwd) => {
|
|
|
6515
7195
|
const deletedPathCandidates = /* @__PURE__ */ new Set([...stagedDeletedPaths, ...unstagedDeletedPaths]);
|
|
6516
7196
|
for (const deletedPath of deletedPathCandidates) {
|
|
6517
7197
|
const change = getOrCreateChange(changesByPath, deletedPath);
|
|
6518
|
-
change.deleted = !await pathExists4(
|
|
7198
|
+
change.deleted = !await pathExists4(path10.join(rootDir, deletedPath));
|
|
6519
7199
|
}
|
|
6520
7200
|
return Array.from(changesByPath.values()).sort((left, right) => {
|
|
6521
7201
|
return left.path.localeCompare(right.path);
|
|
@@ -6609,7 +7289,7 @@ var readBaseFile = async (cwd, base, filePath) => {
|
|
|
6609
7289
|
};
|
|
6610
7290
|
|
|
6611
7291
|
// src/impact/build.ts
|
|
6612
|
-
var
|
|
7292
|
+
var uniqueSorted2 = (values) => [...new Set(values)].sort();
|
|
6613
7293
|
var routeMatchesFile = (route, filePath) => {
|
|
6614
7294
|
return route.codeSurface.some((pattern) => micromatch7.isMatch(filePath, pattern));
|
|
6615
7295
|
};
|
|
@@ -6636,7 +7316,7 @@ var resolveImportPath = (importEdge) => {
|
|
|
6636
7316
|
if (!importEdge.specifier.startsWith(".")) {
|
|
6637
7317
|
return null;
|
|
6638
7318
|
}
|
|
6639
|
-
const basePath =
|
|
7319
|
+
const basePath = path11.posix.normalize(path11.posix.join(path11.posix.dirname(importEdge.from), importEdge.specifier));
|
|
6640
7320
|
const withoutExtension = basePath.replace(/\.[cm]?[jt]sx?$/u, "");
|
|
6641
7321
|
return withoutExtension;
|
|
6642
7322
|
};
|
|
@@ -6649,7 +7329,7 @@ var importTargetsChangedFile = (importEdge, changedPath) => {
|
|
|
6649
7329
|
};
|
|
6650
7330
|
var pathSegments = (filePath) => filePath.split("/").filter(Boolean);
|
|
6651
7331
|
var testHintMatchesChangedFile = (hints, changedPath) => {
|
|
6652
|
-
const changedBaseName =
|
|
7332
|
+
const changedBaseName = path11.posix.basename(changedPath);
|
|
6653
7333
|
const changedSegments = pathSegments(changedPath);
|
|
6654
7334
|
return hints.some((hint) => changedBaseName.startsWith(hint) || changedSegments.includes(hint));
|
|
6655
7335
|
};
|
|
@@ -6741,7 +7421,7 @@ var buildImpactSet = async (cwd, options) => {
|
|
|
6741
7421
|
)
|
|
6742
7422
|
);
|
|
6743
7423
|
}
|
|
6744
|
-
const uniqueAffectedTruthDocs =
|
|
7424
|
+
const uniqueAffectedTruthDocs = uniqueSorted2(affectedTruthDocs);
|
|
6745
7425
|
const changedPaths = changedPathSet(changedFiles);
|
|
6746
7426
|
for (const symbol of changedPublicSymbols) {
|
|
6747
7427
|
if (uniqueAffectedTruthDocs.length === 0) {
|
|
@@ -6796,7 +7476,7 @@ var buildImpactSet = async (cwd, options) => {
|
|
|
6796
7476
|
(left, right) => left.key.localeCompare(right.key)
|
|
6797
7477
|
),
|
|
6798
7478
|
affectedTruthDocs: uniqueAffectedTruthDocs,
|
|
6799
|
-
affectedTests:
|
|
7479
|
+
affectedTests: uniqueSorted2(affectedTests),
|
|
6800
7480
|
changedPublicSymbols: changedPublicSymbols.sort(
|
|
6801
7481
|
(left, right) => `${left.path}:${left.name}:${left.change}`.localeCompare(`${right.path}:${right.name}:${right.change}`)
|
|
6802
7482
|
),
|
|
@@ -6830,27 +7510,18 @@ import fg7 from "fast-glob";
|
|
|
6830
7510
|
|
|
6831
7511
|
// src/evidence/parse.ts
|
|
6832
7512
|
import fs20 from "fs/promises";
|
|
6833
|
-
import
|
|
6834
|
-
import
|
|
7513
|
+
import path12 from "path";
|
|
7514
|
+
import matter4 from "gray-matter";
|
|
6835
7515
|
import { parse as parse3 } from "yaml";
|
|
6836
7516
|
var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
|
|
6837
7517
|
var topLevelEvidenceMarkerPattern = /^evidence\s*:/imu;
|
|
6838
|
-
var repoRootPrefixes = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
|
|
6839
|
-
var normalizeReferencePath = (truthDocPath, referencePath) => {
|
|
6840
|
-
const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
|
|
6841
|
-
const isRepoRelative = repoRootPrefixes.some((prefix) => strippedPath.startsWith(prefix));
|
|
6842
|
-
if (!isRepoRelative && (strippedPath.startsWith(".") || !strippedPath.includes("/"))) {
|
|
6843
|
-
return path11.posix.normalize(path11.posix.join(path11.posix.dirname(truthDocPath), strippedPath));
|
|
6844
|
-
}
|
|
6845
|
-
return path11.posix.normalize(strippedPath);
|
|
6846
|
-
};
|
|
6847
7518
|
var toEvidenceReference = (truthDocPath, raw) => {
|
|
6848
7519
|
if (!raw || typeof raw !== "object" || !("path" in raw) || typeof raw.path !== "string") {
|
|
6849
7520
|
return null;
|
|
6850
7521
|
}
|
|
6851
7522
|
return {
|
|
6852
7523
|
truthDocPath,
|
|
6853
|
-
path:
|
|
7524
|
+
path: normalizeSourceReferencePath(truthDocPath, raw.path),
|
|
6854
7525
|
symbol: "symbol" in raw && typeof raw.symbol === "string" ? raw.symbol : void 0,
|
|
6855
7526
|
startLine: "start_line" in raw && typeof raw.start_line === "number" ? raw.start_line : void 0,
|
|
6856
7527
|
endLine: "end_line" in raw && typeof raw.end_line === "number" ? raw.end_line : void 0,
|
|
@@ -6859,18 +7530,14 @@ var toEvidenceReference = (truthDocPath, raw) => {
|
|
|
6859
7530
|
};
|
|
6860
7531
|
};
|
|
6861
7532
|
var parseEvidenceReferences = async (rootDir, truthDocPath) => {
|
|
6862
|
-
const source = await fs20.readFile(
|
|
6863
|
-
const parsed =
|
|
7533
|
+
const source = await fs20.readFile(path12.join(rootDir, truthDocPath), "utf8");
|
|
7534
|
+
const parsed = matter4(source);
|
|
6864
7535
|
const references = [];
|
|
6865
|
-
const
|
|
6866
|
-
for (const entry of sourceOfTruth) {
|
|
6867
|
-
if (typeof entry !== "string") {
|
|
6868
|
-
continue;
|
|
6869
|
-
}
|
|
7536
|
+
for (const entry of parseSourceReferences(source, truthDocPath)) {
|
|
6870
7537
|
references.push({
|
|
6871
7538
|
truthDocPath,
|
|
6872
|
-
path:
|
|
6873
|
-
source: "
|
|
7539
|
+
path: entry,
|
|
7540
|
+
source: "source-references"
|
|
6874
7541
|
});
|
|
6875
7542
|
}
|
|
6876
7543
|
for (const match of parsed.content.matchAll(yamlFencePattern)) {
|
|
@@ -7245,9 +7912,9 @@ var runCheck = async (cwd, options = {}) => {
|
|
|
7245
7912
|
|
|
7246
7913
|
// src/context-pack/build.ts
|
|
7247
7914
|
import fs22 from "fs/promises";
|
|
7248
|
-
import
|
|
7915
|
+
import path13 from "path";
|
|
7249
7916
|
import fg8 from "fast-glob";
|
|
7250
|
-
var
|
|
7917
|
+
var uniqueSorted3 = (values) => [...new Set(values)].sort();
|
|
7251
7918
|
var repoRootPrefixes2 = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
|
|
7252
7919
|
var isGlobReference2 = (referencePath) => /[*?[\]{}()]/u.test(referencePath);
|
|
7253
7920
|
var normalizeDocReferencePath = (docPath, referencePath) => {
|
|
@@ -7256,12 +7923,12 @@ var normalizeDocReferencePath = (docPath, referencePath) => {
|
|
|
7256
7923
|
return null;
|
|
7257
7924
|
}
|
|
7258
7925
|
const isRepoRelative = repoRootPrefixes2.some((prefix) => strippedPath.startsWith(prefix));
|
|
7259
|
-
const normalized = isRepoRelative ?
|
|
7926
|
+
const normalized = isRepoRelative ? path13.posix.normalize(strippedPath) : path13.posix.normalize(path13.posix.join(path13.posix.dirname(docPath), strippedPath));
|
|
7260
7927
|
return normalized === ".." || normalized.startsWith("../") ? null : normalized;
|
|
7261
7928
|
};
|
|
7262
7929
|
var readIfExists = async (rootDir, filePath) => {
|
|
7263
7930
|
try {
|
|
7264
|
-
return await fs22.readFile(
|
|
7931
|
+
return await fs22.readFile(path13.join(rootDir, filePath), "utf8");
|
|
7265
7932
|
} catch {
|
|
7266
7933
|
return null;
|
|
7267
7934
|
}
|
|
@@ -7295,7 +7962,7 @@ var boundedContent = (filePath, content, warnings) => {
|
|
|
7295
7962
|
};
|
|
7296
7963
|
var documentsFor = async (rootDir, paths, warnings) => {
|
|
7297
7964
|
const documents = [];
|
|
7298
|
-
for (const filePath of
|
|
7965
|
+
for (const filePath of uniqueSorted3(paths)) {
|
|
7299
7966
|
const content = await readIfExists(rootDir, filePath);
|
|
7300
7967
|
if (content !== null) {
|
|
7301
7968
|
const bounded = boundContent(content);
|
|
@@ -7314,7 +7981,7 @@ var documentsFor = async (rootDir, paths, warnings) => {
|
|
|
7314
7981
|
};
|
|
7315
7982
|
var sourceFilesFor = async (rootDir, paths, warnings) => {
|
|
7316
7983
|
const sourceFiles = [];
|
|
7317
|
-
for (const filePath of
|
|
7984
|
+
for (const filePath of uniqueSorted3(paths)) {
|
|
7318
7985
|
const content = await readIfExists(rootDir, filePath);
|
|
7319
7986
|
if (content !== null) {
|
|
7320
7987
|
sourceFiles.push(boundedContent(filePath, content, warnings));
|
|
@@ -7348,16 +8015,16 @@ var sourceOfTruthPathsFor = async (rootDir, docs, truthDocPaths) => {
|
|
|
7348
8015
|
}
|
|
7349
8016
|
}
|
|
7350
8017
|
}
|
|
7351
|
-
return
|
|
8018
|
+
return uniqueSorted3(sourcePaths);
|
|
7352
8019
|
};
|
|
7353
8020
|
var writePathsFor = (workflow, routeIndexPath, truthDocs, routes) => {
|
|
7354
8021
|
if (workflow === "truth-sync") {
|
|
7355
|
-
return
|
|
8022
|
+
return uniqueSorted3([routeIndexPath, ...truthDocs]);
|
|
7356
8023
|
}
|
|
7357
8024
|
if (workflow === "truth-document") {
|
|
7358
|
-
return
|
|
8025
|
+
return uniqueSorted3([routeIndexPath, ...truthDocs]);
|
|
7359
8026
|
}
|
|
7360
|
-
return
|
|
8027
|
+
return uniqueSorted3(routes.flatMap((route) => route.codeSurface));
|
|
7361
8028
|
};
|
|
7362
8029
|
var testCommandsFor = (affectedTests) => {
|
|
7363
8030
|
return affectedTests.length === 0 ? ["npm test"] : [`npm test -- ${affectedTests.join(" ")}`];
|
|
@@ -7380,7 +8047,7 @@ var buildContextPack = async (cwd, options) => {
|
|
|
7380
8047
|
});
|
|
7381
8048
|
}
|
|
7382
8049
|
const sourceOfTruthPaths = await sourceOfTruthPathsFor(rootDir, repoIndex.docs, truthDocPaths);
|
|
7383
|
-
const sourceFilePaths =
|
|
8050
|
+
const sourceFilePaths = uniqueSorted3([
|
|
7384
8051
|
...impactSet?.changedFiles.filter((file) => !file.deleted).map((file) => file.path) ?? [],
|
|
7385
8052
|
...sourceOfTruthPaths
|
|
7386
8053
|
]);
|
|
@@ -7431,7 +8098,7 @@ var renderContextPackMarkdown = (pack) => {
|
|
|
7431
8098
|
import { execa as execa5 } from "execa";
|
|
7432
8099
|
|
|
7433
8100
|
// src/workflow-state/action-context.ts
|
|
7434
|
-
var
|
|
8101
|
+
var uniqueSorted4 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
|
|
7435
8102
|
var helperCommandsFor = (manifestEntry) => (manifestEntry.helpers ?? []).map((helper) => ({
|
|
7436
8103
|
id: helper.id,
|
|
7437
8104
|
runner: helper.runner,
|
|
@@ -7441,8 +8108,8 @@ var helperCommandsFor = (manifestEntry) => (manifestEntry.helpers ?? []).map((he
|
|
|
7441
8108
|
var evidenceFor = (manifestEntry) => manifestEntry.requiredGates.filter((gate) => /evidence|ownership|containment/iu.test(gate));
|
|
7442
8109
|
var baseContext = (manifestEntry, mode, allowedWritePaths, forbiddenWritePaths, writeLeaseRequired) => ({
|
|
7443
8110
|
mode,
|
|
7444
|
-
allowedWritePaths:
|
|
7445
|
-
forbiddenWritePaths:
|
|
8111
|
+
allowedWritePaths: uniqueSorted4(allowedWritePaths),
|
|
8112
|
+
forbiddenWritePaths: uniqueSorted4(forbiddenWritePaths),
|
|
7446
8113
|
stopConditions: [...manifestEntry.negativeTriggers, ...manifestEntry.forbiddenAdjacency],
|
|
7447
8114
|
requiredEvidence: evidenceFor(manifestEntry),
|
|
7448
8115
|
helperValidationCommands: helperCommandsFor(manifestEntry),
|
|
@@ -7504,7 +8171,7 @@ var helperCommandsFor2 = (workflow) => (TRUTHMARK_WORKFLOW_MANIFEST[workflow].he
|
|
|
7504
8171
|
argv: [...helper.command.argv],
|
|
7505
8172
|
optional: helper.optional
|
|
7506
8173
|
}));
|
|
7507
|
-
var
|
|
8174
|
+
var uniqueSorted5 = (values) => [...new Set(values.filter((value) => value.length > 0))].sort();
|
|
7508
8175
|
var isWriteCapable = (workflow) => !["truthmark-preview", "truthmark-check"].includes(workflow);
|
|
7509
8176
|
var DEFAULT_BASE_CANDIDATES = ["@{upstream}", "origin/main", "main", "origin/master", "master"];
|
|
7510
8177
|
var selectComparisonBase = async (rootDir, suppliedBase) => {
|
|
@@ -7522,7 +8189,7 @@ var selectComparisonBase = async (rootDir, suppliedBase) => {
|
|
|
7522
8189
|
}
|
|
7523
8190
|
return null;
|
|
7524
8191
|
};
|
|
7525
|
-
var routeFilesFor = (repoIndex) =>
|
|
8192
|
+
var routeFilesFor = (repoIndex) => uniqueSorted5(repoIndex.routeMap.routes.map((route) => route.sourcePath));
|
|
7526
8193
|
var hasUnmappedFunctionalChange = (impactSet) => impactSet?.diagnostics.some(
|
|
7527
8194
|
(diagnostic) => diagnostic.category === "impact" && /not mapped to a Truthmark route|no affected truth document/u.test(diagnostic.message)
|
|
7528
8195
|
) ?? false;
|
|
@@ -7555,7 +8222,7 @@ var contextDataFor = (workflow, repoIndex, config, impactSet) => {
|
|
|
7555
8222
|
return {};
|
|
7556
8223
|
}
|
|
7557
8224
|
const routeFiles = routeFilesFor(repoIndex);
|
|
7558
|
-
const truthDocs =
|
|
8225
|
+
const truthDocs = uniqueSorted5(
|
|
7559
8226
|
impactSet?.affectedTruthDocs ?? repoIndex.routeMap.routes.flatMap((route) => route.truthDocs)
|
|
7560
8227
|
);
|
|
7561
8228
|
return {
|
|
@@ -7564,7 +8231,7 @@ var contextDataFor = (workflow, repoIndex, config, impactSet) => {
|
|
|
7564
8231
|
truthRoot: config.truthmark.paths.truthRoot,
|
|
7565
8232
|
truthDocs,
|
|
7566
8233
|
starterTruthDocs: workflow === "truthmark-structure" ? truthDocs : [],
|
|
7567
|
-
codeWritePaths: workflow === "truthmark-realize" ?
|
|
8234
|
+
codeWritePaths: workflow === "truthmark-realize" ? uniqueSorted5(impactSet?.affectedRoutes.flatMap((route) => route.codeSurface) ?? []) : [],
|
|
7568
8235
|
portalEnabled: config.truthmark.generated.portal.enabled,
|
|
7569
8236
|
portalOutputPath: config.truthmark.paths.portalOutput,
|
|
7570
8237
|
routes: repoIndex.routeMap.routes
|