truthmark 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +18 -21
- package/README.es.md +18 -21
- package/README.md +18 -21
- package/README.ru.md +18 -21
- package/README.zh.md +18 -21
- package/dist/main.js +1335 -1135
- package/dist/main.js.map +1 -1
- package/package.json +4 -2
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 path12 = 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 path12 === "string" ? inferTruthDocumentKindFromPath(path12, options) : null;
|
|
625
|
+
const inferredLane = typeof path12 === "string" ? inferTruthDocumentLaneFromPath(path12, options) : null;
|
|
626
|
+
const normalizedKind = isTruthDocumentKind(kind) ? kind : inferredKind;
|
|
627
|
+
const normalizedLane = lane === "product" || lane === "engineering" ? lane : normalizedKind ? laneForTruthDocumentKind(normalizedKind) : inferredLane;
|
|
628
|
+
if (typeof path12 !== "string" || path12.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: path12.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,26 +2009,66 @@ 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."
|
|
1777
2060
|
].join("\n");
|
|
1778
2061
|
var REPOSITORY_INTELLIGENCE_INSTRUCTIONS = [
|
|
1779
|
-
"Repository intelligence artifacts are optional derived context: RepoIndex, RouteMap, ImpactSet, and
|
|
2062
|
+
"Repository intelligence artifacts are optional derived context: RepoIndex, RouteMap, ImpactSet, and WorkflowState/action context may guide routing, write boundaries, and verification planning when available.",
|
|
1780
2063
|
"They do not override checkout evidence, canonical truth docs, route files, or workflow write boundaries.",
|
|
1781
2064
|
"If unavailable, inspect any present Truthmark config, route files, source files, truth docs, and tests directly, then report that repository-intelligence artifacts were not generated."
|
|
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"
|
|
@@ -3042,7 +3275,7 @@ ${renderTruthDocOwnershipGateSection(
|
|
|
3042
3275
|
${TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS}
|
|
3043
3276
|
${FEATURE_DOC_TEMPLATE_INSTRUCTIONS}
|
|
3044
3277
|
${renderTruthDocRestructureGateSection(
|
|
3045
|
-
"Truth Sync may restructure
|
|
3278
|
+
"Truth Sync may restructure leased canonical truth docs when the current sync evidence shows repository truth is stale, even when the doc is outside the initially affected route focus."
|
|
3046
3279
|
)}
|
|
3047
3280
|
${ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS}
|
|
3048
3281
|
${renderRouteFirstEvidenceGateSection(
|
|
@@ -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.
|
|
@@ -3069,34 +3302,9 @@ Parent post-sync verification:
|
|
|
3069
3302
|
- block if functional code changed during sync
|
|
3070
3303
|
- 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
|
|
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
|
-
- verify the updated docs correspond to
|
|
3305
|
+
- verify the updated docs correspond to reviewed checkout evidence, changed-code impact, or a recorded stale-truth correction made within the sync write lease
|
|
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,7 +3390,31 @@ description: '${description}'
|
|
|
3182
3390
|
${prompt}
|
|
3183
3391
|
`;
|
|
3184
3392
|
};
|
|
3185
|
-
var
|
|
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
|
+
};
|
|
3417
|
+
var renderTomlString = (value) => {
|
|
3186
3418
|
return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"')}"`;
|
|
3187
3419
|
};
|
|
3188
3420
|
var renderTomlStringArray = (values) => {
|
|
@@ -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
3821
|
const subagents = renderWorkflowSubagentSupport(workflowId, host);
|
|
3501
3822
|
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
|
-
];
|
|
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 = (path12) => {
|
|
3869
|
+
const normalized = path12.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((path12) => ({
|
|
4998
|
+
path: path12,
|
|
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, path12 = "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, path12), "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, path12, upsertManagedBlock(existingContent, block));
|
|
4816
5132
|
};
|
|
4817
5133
|
var diagnosticCategoryForPath = (filePath, config) => {
|
|
4818
5134
|
if (filePath === "AGENTS.md") {
|
|
@@ -5078,8 +5394,53 @@ var checkAuthority = async (rootDir, config) => {
|
|
|
5078
5394
|
// src/checks/frontmatter.ts
|
|
5079
5395
|
import fs10 from "fs/promises";
|
|
5080
5396
|
|
|
5397
|
+
// src/markdown/frontmatter.ts
|
|
5398
|
+
import { parse as parse3 } from "yaml";
|
|
5399
|
+
var openingDelimiterPattern = /^(?:\uFEFF)?---[ \t]*(?:\r?\n|$)/u;
|
|
5400
|
+
var closingDelimiterPattern = /^---[ \t]*(?:\r?\n|$)/mu;
|
|
5401
|
+
var asRecord = (value) => {
|
|
5402
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
5403
|
+
return value;
|
|
5404
|
+
}
|
|
5405
|
+
return {};
|
|
5406
|
+
};
|
|
5407
|
+
var parseFrontmatter = (source, options = {}) => {
|
|
5408
|
+
const openingMatch = openingDelimiterPattern.exec(source);
|
|
5409
|
+
if (!openingMatch) {
|
|
5410
|
+
return {
|
|
5411
|
+
data: {},
|
|
5412
|
+
content: source.replace(/^\uFEFF/u, "")
|
|
5413
|
+
};
|
|
5414
|
+
}
|
|
5415
|
+
const bodyStart = openingMatch[0].length;
|
|
5416
|
+
const bodyAndContent = source.slice(bodyStart);
|
|
5417
|
+
const closingMatch = closingDelimiterPattern.exec(bodyAndContent);
|
|
5418
|
+
if (!closingMatch || typeof closingMatch.index !== "number") {
|
|
5419
|
+
return {
|
|
5420
|
+
data: {},
|
|
5421
|
+
content: source.replace(/^\uFEFF/u, "")
|
|
5422
|
+
};
|
|
5423
|
+
}
|
|
5424
|
+
const yamlSource = bodyAndContent.slice(0, closingMatch.index);
|
|
5425
|
+
const content = bodyAndContent.slice(closingMatch.index + closingMatch[0].length);
|
|
5426
|
+
let data = {};
|
|
5427
|
+
if (yamlSource.trim().length > 0) {
|
|
5428
|
+
try {
|
|
5429
|
+
data = asRecord(parse3(yamlSource));
|
|
5430
|
+
} catch (error) {
|
|
5431
|
+
if (options.throwOnInvalid) {
|
|
5432
|
+
throw error;
|
|
5433
|
+
}
|
|
5434
|
+
data = {};
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
return {
|
|
5438
|
+
data,
|
|
5439
|
+
content
|
|
5440
|
+
};
|
|
5441
|
+
};
|
|
5442
|
+
|
|
5081
5443
|
// src/markdown/parse.ts
|
|
5082
|
-
import matter from "gray-matter";
|
|
5083
5444
|
import { unified } from "unified";
|
|
5084
5445
|
import remarkParse from "remark-parse";
|
|
5085
5446
|
import { visit } from "unist-util-visit";
|
|
@@ -5093,7 +5454,7 @@ var isInternalLink = (url) => {
|
|
|
5093
5454
|
return url.startsWith("#") || !url.includes("://") && !url.startsWith("mailto:");
|
|
5094
5455
|
};
|
|
5095
5456
|
var parseMarkdownDocument = (source) => {
|
|
5096
|
-
const parsed =
|
|
5457
|
+
const parsed = parseFrontmatter(source);
|
|
5097
5458
|
const tree = unified().use(remarkParse).parse(parsed.content);
|
|
5098
5459
|
const headings = [];
|
|
5099
5460
|
const internalLinks = [];
|
|
@@ -5117,6 +5478,11 @@ var parseMarkdownDocument = (source) => {
|
|
|
5117
5478
|
|
|
5118
5479
|
// src/checks/frontmatter.ts
|
|
5119
5480
|
var isTruthDocumentKind2 = (value) => TRUTH_DOCUMENT_KINDS.includes(value);
|
|
5481
|
+
var ROUTE_RELATIONSHIP_FRONTMATTER_FIELDS = [
|
|
5482
|
+
"realized_by",
|
|
5483
|
+
"realizes",
|
|
5484
|
+
"depends_on"
|
|
5485
|
+
];
|
|
5120
5486
|
var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntries = []) => {
|
|
5121
5487
|
const diagnostics = [];
|
|
5122
5488
|
const truthDocumentMap = new Map(
|
|
@@ -5131,6 +5497,7 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
|
|
|
5131
5497
|
const source = await fs10.readFile(absolutePath, "utf8");
|
|
5132
5498
|
let document;
|
|
5133
5499
|
try {
|
|
5500
|
+
parseFrontmatter(source, { throwOnInvalid: true });
|
|
5134
5501
|
document = parseMarkdownDocument(source);
|
|
5135
5502
|
} catch (error) {
|
|
5136
5503
|
diagnostics.push({
|
|
@@ -5163,6 +5530,28 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
|
|
|
5163
5530
|
}
|
|
5164
5531
|
const routedTruthDocument = truthDocumentMap.get(markdownPath);
|
|
5165
5532
|
const truthKind = document.frontmatter.truth_kind;
|
|
5533
|
+
const truthLane = document.frontmatter.truth_lane;
|
|
5534
|
+
const isTruthDocument = routedTruthDocument !== void 0 || truthKind !== void 0;
|
|
5535
|
+
if (isTruthDocument) {
|
|
5536
|
+
for (const field of ROUTE_RELATIONSHIP_FRONTMATTER_FIELDS) {
|
|
5537
|
+
if (field in document.frontmatter) {
|
|
5538
|
+
diagnostics.push({
|
|
5539
|
+
category: "frontmatter",
|
|
5540
|
+
severity: "error",
|
|
5541
|
+
message: `Frontmatter field ${field} is not allowed on truth documents; author relationship metadata in fenced route YAML entries instead.`,
|
|
5542
|
+
file: markdownPath
|
|
5543
|
+
});
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
if (truthLane !== void 0 && truthLane !== "product" && truthLane !== "engineering") {
|
|
5548
|
+
diagnostics.push({
|
|
5549
|
+
category: "frontmatter",
|
|
5550
|
+
severity: "error",
|
|
5551
|
+
message: "Frontmatter truth_lane must be product or engineering.",
|
|
5552
|
+
file: markdownPath
|
|
5553
|
+
});
|
|
5554
|
+
}
|
|
5166
5555
|
if (truthKind !== void 0) {
|
|
5167
5556
|
if (typeof truthKind !== "string" || !isTruthDocumentKind2(truthKind)) {
|
|
5168
5557
|
diagnostics.push({
|
|
@@ -5181,6 +5570,14 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
|
|
|
5181
5570
|
file: markdownPath
|
|
5182
5571
|
});
|
|
5183
5572
|
}
|
|
5573
|
+
if (typeof truthKind === "string" && isTruthDocumentKind2(truthKind) && truthLane !== void 0 && (truthLane === "product" || truthLane === "engineering") && truthLane !== laneForTruthDocumentKind(truthKind)) {
|
|
5574
|
+
diagnostics.push({
|
|
5575
|
+
category: "frontmatter",
|
|
5576
|
+
severity: "error",
|
|
5577
|
+
message: `Frontmatter truth_lane ${truthLane} must match truth_kind ${truthKind}.`,
|
|
5578
|
+
file: markdownPath
|
|
5579
|
+
});
|
|
5580
|
+
}
|
|
5184
5581
|
}
|
|
5185
5582
|
}
|
|
5186
5583
|
return diagnostics;
|
|
@@ -5340,7 +5737,9 @@ var resolveAreaRouting = async (rootDir, config) => {
|
|
|
5340
5737
|
};
|
|
5341
5738
|
}
|
|
5342
5739
|
const rootParsed = parseAreasMarkdown(rootRead.source ?? "", {
|
|
5343
|
-
truthDocsRoot: config.truthDocsRoot
|
|
5740
|
+
truthDocsRoot: config.truthDocsRoot,
|
|
5741
|
+
productTruthRoot: config.productTruthRoot,
|
|
5742
|
+
engineeringTruthRoot: config.engineeringTruthRoot
|
|
5344
5743
|
});
|
|
5345
5744
|
diagnostics.push(
|
|
5346
5745
|
...rootParsed.diagnostics.map((diagnostic) => ({
|
|
@@ -5379,7 +5778,9 @@ var resolveAreaRouting = async (rootDir, config) => {
|
|
|
5379
5778
|
}
|
|
5380
5779
|
routeFiles.push(areaFile);
|
|
5381
5780
|
const childParsed = parseAreasMarkdown(childRead.source ?? "", {
|
|
5382
|
-
truthDocsRoot: config.truthDocsRoot
|
|
5781
|
+
truthDocsRoot: config.truthDocsRoot,
|
|
5782
|
+
productTruthRoot: config.productTruthRoot,
|
|
5783
|
+
engineeringTruthRoot: config.engineeringTruthRoot
|
|
5383
5784
|
});
|
|
5384
5785
|
diagnostics.push(
|
|
5385
5786
|
...childParsed.diagnostics.map((diagnostic) => ({
|
|
@@ -5656,11 +6057,115 @@ var BROAD_CODE_SURFACES = /* @__PURE__ */ new Set([
|
|
|
5656
6057
|
var isBroadCodeSurface = (pattern) => {
|
|
5657
6058
|
return BROAD_CODE_SURFACES.has(pattern.replace(/\/\*\*\/\*$/u, "/**"));
|
|
5658
6059
|
};
|
|
6060
|
+
var PRODUCT_LINK_REVIEW_KINDS = /* @__PURE__ */ new Set([
|
|
6061
|
+
"engineering-behavior",
|
|
6062
|
+
"engineering-workflow",
|
|
6063
|
+
"engineering-contract"
|
|
6064
|
+
]);
|
|
6065
|
+
var normalizeRoot = (value) => value.replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
6066
|
+
var validateLaneShape = (entry, config, areaName) => {
|
|
6067
|
+
const diagnostics = [];
|
|
6068
|
+
const productRoot = normalizeRoot(resolveProductTruthRoot(config));
|
|
6069
|
+
const engineeringRoot = normalizeRoot(resolveEngineeringTruthRoot(config));
|
|
6070
|
+
const normalizedPath = entry.path.replaceAll("\\", "/");
|
|
6071
|
+
const expectedLane = laneForTruthDocumentKind(entry.kind);
|
|
6072
|
+
if (entry.lane !== expectedLane) {
|
|
6073
|
+
diagnostics.push({
|
|
6074
|
+
category: "lane-shape",
|
|
6075
|
+
severity: "error",
|
|
6076
|
+
message: `Truth document ${entry.path} declares ${entry.kind} in ${entry.lane} lane; ${entry.kind} belongs to the ${expectedLane} lane.`,
|
|
6077
|
+
area: areaName,
|
|
6078
|
+
file: entry.path
|
|
6079
|
+
});
|
|
6080
|
+
}
|
|
6081
|
+
if (entry.lane === "product" && !normalizedPath.startsWith(`${productRoot}/`)) {
|
|
6082
|
+
diagnostics.push({
|
|
6083
|
+
category: "lane-shape",
|
|
6084
|
+
severity: "error",
|
|
6085
|
+
message: `Product truth document ${entry.path} must live under ${productRoot}.`,
|
|
6086
|
+
area: areaName,
|
|
6087
|
+
file: entry.path
|
|
6088
|
+
});
|
|
6089
|
+
}
|
|
6090
|
+
if (entry.lane === "engineering" && !normalizedPath.startsWith(`${engineeringRoot}/`)) {
|
|
6091
|
+
diagnostics.push({
|
|
6092
|
+
category: "lane-shape",
|
|
6093
|
+
severity: "error",
|
|
6094
|
+
message: `Engineering truth document ${entry.path} must live under ${engineeringRoot}.`,
|
|
6095
|
+
area: areaName,
|
|
6096
|
+
file: entry.path
|
|
6097
|
+
});
|
|
6098
|
+
}
|
|
6099
|
+
return diagnostics;
|
|
6100
|
+
};
|
|
6101
|
+
var validateRelationshipTargets = (entries) => {
|
|
6102
|
+
const diagnostics = [];
|
|
6103
|
+
const byPath = new Map(entries.map((entry) => [entry.path, entry]));
|
|
6104
|
+
for (const entry of entries) {
|
|
6105
|
+
for (const target of entry.realizedBy) {
|
|
6106
|
+
const targetEntry = byPath.get(target);
|
|
6107
|
+
if (!targetEntry) {
|
|
6108
|
+
diagnostics.push({
|
|
6109
|
+
category: "traceability",
|
|
6110
|
+
severity: "error",
|
|
6111
|
+
message: `Product truth document ${entry.path} declares missing engineering realization ${target}.`,
|
|
6112
|
+
file: entry.path
|
|
6113
|
+
});
|
|
6114
|
+
} else if (targetEntry.lane !== "engineering") {
|
|
6115
|
+
diagnostics.push({
|
|
6116
|
+
category: "traceability",
|
|
6117
|
+
severity: "error",
|
|
6118
|
+
message: `Product truth document ${entry.path} realized_by target ${target} must point to engineering truth.`,
|
|
6119
|
+
file: entry.path
|
|
6120
|
+
});
|
|
6121
|
+
}
|
|
6122
|
+
}
|
|
6123
|
+
for (const target of entry.realizes) {
|
|
6124
|
+
const targetEntry = byPath.get(target);
|
|
6125
|
+
if (!targetEntry) {
|
|
6126
|
+
diagnostics.push({
|
|
6127
|
+
category: "traceability",
|
|
6128
|
+
severity: "error",
|
|
6129
|
+
message: `Engineering truth document ${entry.path} declares missing product truth ${target}.`,
|
|
6130
|
+
file: entry.path
|
|
6131
|
+
});
|
|
6132
|
+
} else if (targetEntry.lane !== "product") {
|
|
6133
|
+
diagnostics.push({
|
|
6134
|
+
category: "traceability",
|
|
6135
|
+
severity: "error",
|
|
6136
|
+
message: `Engineering truth document ${entry.path} realizes target ${target} must point to product truth.`,
|
|
6137
|
+
file: entry.path
|
|
6138
|
+
});
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
return diagnostics;
|
|
6143
|
+
};
|
|
6144
|
+
var validateMissingProductLinkReviews = (entries, reportedPaths) => {
|
|
6145
|
+
const diagnostics = [];
|
|
6146
|
+
const hasProductTruth = entries.some((entry) => entry.lane === "product");
|
|
6147
|
+
if (!hasProductTruth) {
|
|
6148
|
+
return diagnostics;
|
|
6149
|
+
}
|
|
6150
|
+
for (const entry of entries) {
|
|
6151
|
+
if (entry.lane === "engineering" && PRODUCT_LINK_REVIEW_KINDS.has(entry.kind) && entry.realizes.length === 0 && !reportedPaths.has(entry.path)) {
|
|
6152
|
+
reportedPaths.add(entry.path);
|
|
6153
|
+
diagnostics.push({
|
|
6154
|
+
category: "traceability",
|
|
6155
|
+
severity: "review",
|
|
6156
|
+
message: `User-visible engineering truth document ${entry.path} should link product truth with realizes when it implements a product capability.`,
|
|
6157
|
+
file: entry.path
|
|
6158
|
+
});
|
|
6159
|
+
}
|
|
6160
|
+
}
|
|
6161
|
+
return diagnostics;
|
|
6162
|
+
};
|
|
5659
6163
|
var checkAreas = async (rootDir, config) => {
|
|
5660
6164
|
const routing = await resolveAreaRouting(rootDir, {
|
|
5661
6165
|
rootIndex: config.truthmark.paths.routesIndex,
|
|
5662
6166
|
areaFilesRoot: config.truthmark.paths.routeAreasRoot,
|
|
5663
|
-
|
|
6167
|
+
productTruthRoot: resolveProductTruthRoot(config),
|
|
6168
|
+
engineeringTruthRoot: resolveEngineeringTruthRoot(config)
|
|
5664
6169
|
});
|
|
5665
6170
|
const discoveredCodeFiles = await fg4([...COVERAGE_SCAN_PATTERNS], {
|
|
5666
6171
|
cwd: rootDir,
|
|
@@ -5676,6 +6181,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5676
6181
|
const truthDocumentPaths = [];
|
|
5677
6182
|
const seenTruthDocumentPaths = /* @__PURE__ */ new Set();
|
|
5678
6183
|
const truthDocumentEntryMap = /* @__PURE__ */ new Map();
|
|
6184
|
+
const reportedMissingProductLinkPaths = /* @__PURE__ */ new Set();
|
|
5679
6185
|
const areaCoverage = routing.areas.map((area) => ({
|
|
5680
6186
|
area,
|
|
5681
6187
|
valid: true,
|
|
@@ -5693,6 +6199,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5693
6199
|
const truthReferences = routing.truthDocumentReferences;
|
|
5694
6200
|
for (const area of truthReferences) {
|
|
5695
6201
|
let areaHasTruthDocumentErrors = false;
|
|
6202
|
+
const areaTruthDocumentEntries = [];
|
|
5696
6203
|
const registerTruthDocumentEntry = (truthDocumentEntry) => {
|
|
5697
6204
|
const existingEntry = truthDocumentEntryMap.get(truthDocumentEntry.path);
|
|
5698
6205
|
if (existingEntry && existingEntry.kind !== truthDocumentEntry.kind) {
|
|
@@ -5705,16 +6212,39 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5705
6212
|
});
|
|
5706
6213
|
return false;
|
|
5707
6214
|
}
|
|
6215
|
+
if (existingEntry && existingEntry.lane !== truthDocumentEntry.lane) {
|
|
6216
|
+
diagnostics.push({
|
|
6217
|
+
category: "area-index",
|
|
6218
|
+
severity: "error",
|
|
6219
|
+
message: `Truth document ${truthDocumentEntry.path} is routed with conflicting lanes ${existingEntry.lane} and ${truthDocumentEntry.lane}.`,
|
|
6220
|
+
area: area.name,
|
|
6221
|
+
file: truthDocumentEntry.path
|
|
6222
|
+
});
|
|
6223
|
+
return false;
|
|
6224
|
+
}
|
|
5708
6225
|
if (!existingEntry) {
|
|
5709
6226
|
truthDocumentEntryMap.set(truthDocumentEntry.path, truthDocumentEntry);
|
|
6227
|
+
} else {
|
|
6228
|
+
truthDocumentEntryMap.set(
|
|
6229
|
+
truthDocumentEntry.path,
|
|
6230
|
+
mergeTruthDocumentEntryRelationships(
|
|
6231
|
+
existingEntry,
|
|
6232
|
+
truthDocumentEntry
|
|
6233
|
+
)
|
|
6234
|
+
);
|
|
5710
6235
|
}
|
|
6236
|
+
areaTruthDocumentEntries.push(truthDocumentEntry);
|
|
6237
|
+
diagnostics.push(
|
|
6238
|
+
...validateLaneShape(truthDocumentEntry, config, area.name)
|
|
6239
|
+
);
|
|
5711
6240
|
return true;
|
|
5712
6241
|
};
|
|
5713
|
-
for (const
|
|
6242
|
+
for (const [
|
|
6243
|
+
truthDocumentIndex,
|
|
6244
|
+
truthDocument
|
|
6245
|
+
] of area.truthDocuments.entries()) {
|
|
6246
|
+
const routedEntry = area.truthDocumentEntries[truthDocumentIndex];
|
|
5714
6247
|
if (looksLikeGlob2(truthDocument)) {
|
|
5715
|
-
const routedGlobEntry = area.truthDocumentEntries.find(
|
|
5716
|
-
(entry) => entry.path === truthDocument
|
|
5717
|
-
);
|
|
5718
6248
|
const matches = (await fg4([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
|
|
5719
6249
|
if (matches.length === 0) {
|
|
5720
6250
|
diagnostics.push({
|
|
@@ -5746,8 +6276,8 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5746
6276
|
seenTruthDocumentPaths.add(match);
|
|
5747
6277
|
truthDocumentPaths.push(match);
|
|
5748
6278
|
}
|
|
5749
|
-
if (
|
|
5750
|
-
...
|
|
6279
|
+
if (routedEntry && !registerTruthDocumentEntry({
|
|
6280
|
+
...routedEntry,
|
|
5751
6281
|
path: match
|
|
5752
6282
|
})) {
|
|
5753
6283
|
areaHasTruthDocumentErrors = true;
|
|
@@ -5785,7 +6315,6 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5785
6315
|
seenTruthDocumentPaths.add(truthDocument);
|
|
5786
6316
|
truthDocumentPaths.push(truthDocument);
|
|
5787
6317
|
}
|
|
5788
|
-
const routedEntry = area.truthDocumentEntries.find((entry) => entry.path === truthDocument);
|
|
5789
6318
|
if (routedEntry && !registerTruthDocumentEntry(routedEntry)) {
|
|
5790
6319
|
areaHasTruthDocumentErrors = true;
|
|
5791
6320
|
}
|
|
@@ -5800,6 +6329,12 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5800
6329
|
matchingArea.valid = false;
|
|
5801
6330
|
}
|
|
5802
6331
|
}
|
|
6332
|
+
diagnostics.push(
|
|
6333
|
+
...validateMissingProductLinkReviews(
|
|
6334
|
+
areaTruthDocumentEntries,
|
|
6335
|
+
reportedMissingProductLinkPaths
|
|
6336
|
+
)
|
|
6337
|
+
);
|
|
5803
6338
|
}
|
|
5804
6339
|
for (const entry of areaCoverage) {
|
|
5805
6340
|
const { area } = entry;
|
|
@@ -5826,7 +6361,10 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5826
6361
|
let containedMatches = 0;
|
|
5827
6362
|
for (const match of matches) {
|
|
5828
6363
|
try {
|
|
5829
|
-
await assertRepoContainment(
|
|
6364
|
+
await assertRepoContainment(
|
|
6365
|
+
rootDir,
|
|
6366
|
+
resolveRepoPath(rootDir, match)
|
|
6367
|
+
);
|
|
5830
6368
|
containedMatches += 1;
|
|
5831
6369
|
} catch {
|
|
5832
6370
|
diagnostics.push({
|
|
@@ -5898,10 +6436,12 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5898
6436
|
const topologyPressureCount = broadAreaCount + diagnostics.filter(
|
|
5899
6437
|
(diagnostic) => diagnostic.category === "area-index" && diagnostic.severity === "review"
|
|
5900
6438
|
).length;
|
|
6439
|
+
const truthDocumentEntries = [...truthDocumentEntryMap.values()];
|
|
6440
|
+
diagnostics.push(...validateRelationshipTargets(truthDocumentEntries));
|
|
5901
6441
|
return {
|
|
5902
6442
|
diagnostics,
|
|
5903
6443
|
truthDocumentPaths,
|
|
5904
|
-
truthDocumentEntries
|
|
6444
|
+
truthDocumentEntries,
|
|
5905
6445
|
routePrecision: {
|
|
5906
6446
|
leafAreaCount: routing.areas.length,
|
|
5907
6447
|
broadAreaCount
|
|
@@ -5913,7 +6453,38 @@ var checkAreas = async (rootDir, config) => {
|
|
|
5913
6453
|
// src/checks/decisions.ts
|
|
5914
6454
|
import fs14 from "fs/promises";
|
|
5915
6455
|
import micromatch5 from "micromatch";
|
|
5916
|
-
var
|
|
6456
|
+
var PRODUCT_CAPABILITY_REQUIRED_HEADINGS = [
|
|
6457
|
+
"Capability Promise",
|
|
6458
|
+
"Users And Value",
|
|
6459
|
+
"Capability Scope",
|
|
6460
|
+
"Current Product Behavior",
|
|
6461
|
+
"Acceptance Criteria",
|
|
6462
|
+
"Product Decisions",
|
|
6463
|
+
"Engineering Realization Links",
|
|
6464
|
+
"Non-Goals"
|
|
6465
|
+
];
|
|
6466
|
+
var ENGINEERING_REQUIRED_HEADINGS = [
|
|
6467
|
+
"Purpose",
|
|
6468
|
+
"Scope",
|
|
6469
|
+
"Current Implementation Behavior",
|
|
6470
|
+
"Source References",
|
|
6471
|
+
"Product Truth Links",
|
|
6472
|
+
"Maintenance Notes"
|
|
6473
|
+
];
|
|
6474
|
+
var FORBIDDEN_PRODUCT_HEADINGS = [
|
|
6475
|
+
"Execution Flow",
|
|
6476
|
+
"Execution Model",
|
|
6477
|
+
"Generated File Inventory",
|
|
6478
|
+
"CLI Envelope Details",
|
|
6479
|
+
"Data And Control Flow"
|
|
6480
|
+
];
|
|
6481
|
+
var FORBIDDEN_ENGINEERING_HEADINGS = [
|
|
6482
|
+
"Product Promise",
|
|
6483
|
+
"User / Stakeholder Value",
|
|
6484
|
+
"Product Decisions",
|
|
6485
|
+
"Product Rationale",
|
|
6486
|
+
"Business Boundary"
|
|
6487
|
+
];
|
|
5917
6488
|
var isTruthDocumentKind3 = (value) => {
|
|
5918
6489
|
return TRUTH_DOCUMENT_KINDS.includes(value);
|
|
5919
6490
|
};
|
|
@@ -5921,16 +6492,18 @@ var escapeRegExp2 = (value) => {
|
|
|
5921
6492
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5922
6493
|
};
|
|
5923
6494
|
var hasHeading = (source, heading) => {
|
|
5924
|
-
return new RegExp(`^#{2,3}\\s+${escapeRegExp2(heading)}\\s*$`, "mu").test(
|
|
6495
|
+
return new RegExp(`^#{2,3}\\s+${escapeRegExp2(heading)}\\s*$`, "mu").test(
|
|
6496
|
+
source
|
|
6497
|
+
);
|
|
5925
6498
|
};
|
|
5926
6499
|
var kindSpecificHeadingMessages = (source, kind) => {
|
|
5927
6500
|
if (kind === null) {
|
|
5928
6501
|
return [];
|
|
5929
6502
|
}
|
|
5930
|
-
if (kind === "behavior") {
|
|
5931
|
-
return hasHeading(source, "Current Behavior") ? [] : ["Current Behavior"];
|
|
6503
|
+
if (kind === "engineering-behavior") {
|
|
6504
|
+
return hasHeading(source, "Current Implementation Behavior") ? [] : ["Current Implementation Behavior"];
|
|
5932
6505
|
}
|
|
5933
|
-
if (kind === "contract") {
|
|
6506
|
+
if (kind === "engineering-contract") {
|
|
5934
6507
|
const missingMessages = [];
|
|
5935
6508
|
if (!hasHeading(source, "Contract Surface")) {
|
|
5936
6509
|
missingMessages.push("Contract Surface");
|
|
@@ -5940,10 +6513,10 @@ var kindSpecificHeadingMessages = (source, kind) => {
|
|
|
5940
6513
|
}
|
|
5941
6514
|
return missingMessages;
|
|
5942
6515
|
}
|
|
5943
|
-
if (kind === "architecture") {
|
|
6516
|
+
if (kind === "engineering-architecture") {
|
|
5944
6517
|
return hasHeading(source, "Boundaries") || hasHeading(source, "Components") ? [] : ["Boundaries or Components"];
|
|
5945
6518
|
}
|
|
5946
|
-
if (kind === "workflow") {
|
|
6519
|
+
if (kind === "engineering-workflow") {
|
|
5947
6520
|
const missingMessages = [];
|
|
5948
6521
|
if (!hasHeading(source, "Triggers")) {
|
|
5949
6522
|
missingMessages.push("Triggers");
|
|
@@ -5953,23 +6526,34 @@ var kindSpecificHeadingMessages = (source, kind) => {
|
|
|
5953
6526
|
}
|
|
5954
6527
|
return missingMessages;
|
|
5955
6528
|
}
|
|
5956
|
-
if (kind === "operations") {
|
|
6529
|
+
if (kind === "engineering-operations") {
|
|
5957
6530
|
return hasHeading(source, "Runtime Topology") || hasHeading(source, "Configuration") ? [] : ["Runtime Topology or Configuration"];
|
|
5958
6531
|
}
|
|
5959
|
-
if (kind === "test-behavior") {
|
|
6532
|
+
if (kind === "engineering-test-behavior") {
|
|
5960
6533
|
const missingMessages = [];
|
|
5961
6534
|
if (!hasHeading(source, "Execution Model")) {
|
|
5962
6535
|
missingMessages.push("Execution Model");
|
|
5963
6536
|
}
|
|
5964
6537
|
if (!hasHeading(source, "Fixtures And Data Model") && !hasHeading(source, "Assertions And Invariants")) {
|
|
5965
|
-
missingMessages.push(
|
|
6538
|
+
missingMessages.push(
|
|
6539
|
+
"Fixtures And Data Model or Assertions And Invariants"
|
|
6540
|
+
);
|
|
5966
6541
|
}
|
|
5967
6542
|
return missingMessages;
|
|
5968
6543
|
}
|
|
5969
6544
|
return [];
|
|
5970
6545
|
};
|
|
6546
|
+
var productRequiredHeadings = (kind) => {
|
|
6547
|
+
if (kind === "product-capability") {
|
|
6548
|
+
return PRODUCT_CAPABILITY_REQUIRED_HEADINGS;
|
|
6549
|
+
}
|
|
6550
|
+
return PRODUCT_CAPABILITY_REQUIRED_HEADINGS;
|
|
6551
|
+
};
|
|
5971
6552
|
var decisionTruthGlobs = (config) => {
|
|
5972
|
-
return [
|
|
6553
|
+
return [
|
|
6554
|
+
`${resolveProductTruthRoot(config)}/**/*.md`,
|
|
6555
|
+
`${resolveEngineeringTruthRoot(config)}/**/*.md`
|
|
6556
|
+
];
|
|
5973
6557
|
};
|
|
5974
6558
|
var isDecisionTruthCandidate = (config, filePath) => {
|
|
5975
6559
|
return !filePath.endsWith("/README.md") && micromatch5.isMatch(filePath, decisionTruthGlobs(config));
|
|
@@ -5983,25 +6567,43 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
|
|
|
5983
6567
|
(filePath) => truthDocumentMap.has(filePath) || isDecisionTruthCandidate(config, filePath)
|
|
5984
6568
|
).sort();
|
|
5985
6569
|
for (const filePath of candidatePaths) {
|
|
5986
|
-
const source = await fs14.readFile(
|
|
6570
|
+
const source = await fs14.readFile(
|
|
6571
|
+
resolveRepoPath(rootDir, filePath),
|
|
6572
|
+
"utf8"
|
|
6573
|
+
);
|
|
5987
6574
|
const document = parseMarkdownDocument(source);
|
|
5988
6575
|
const routedTruthDocument = truthDocumentMap.get(filePath);
|
|
5989
6576
|
const frontmatterTruthKind = typeof document.frontmatter.truth_kind === "string" ? document.frontmatter.truth_kind : null;
|
|
5990
6577
|
const routedTruthKind = routedTruthDocument?.kindSource === "defaulted" ? null : routedTruthDocument?.kind;
|
|
5991
6578
|
const truthKind = routedTruthKind ?? (frontmatterTruthKind && isTruthDocumentKind3(frontmatterTruthKind) ? frontmatterTruthKind : inferTruthDocumentKindFromPath(filePath));
|
|
5992
|
-
const
|
|
6579
|
+
const lane = routedTruthDocument?.lane ?? (truthKind ? laneForTruthDocumentKind(truthKind) : filePath.startsWith(resolveProductTruthRoot(config)) ? "product" : "engineering");
|
|
6580
|
+
const requiredHeadings = lane === "product" ? productRequiredHeadings(truthKind) : ENGINEERING_REQUIRED_HEADINGS;
|
|
6581
|
+
const missingHeadings = requiredHeadings.filter(
|
|
5993
6582
|
(heading) => !hasHeading(source, heading)
|
|
5994
6583
|
);
|
|
5995
|
-
|
|
5996
|
-
|
|
6584
|
+
if (lane === "engineering") {
|
|
6585
|
+
missingHeadings.push(...kindSpecificHeadingMessages(source, truthKind));
|
|
6586
|
+
}
|
|
6587
|
+
const forbiddenHeadings = (lane === "product" ? FORBIDDEN_PRODUCT_HEADINGS : FORBIDDEN_ENGINEERING_HEADINGS).filter((heading) => hasHeading(source, heading));
|
|
6588
|
+
if (missingHeadings.length === 0 && forbiddenHeadings.length === 0) {
|
|
5997
6589
|
continue;
|
|
5998
6590
|
}
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6591
|
+
if (missingHeadings.length > 0) {
|
|
6592
|
+
diagnostics.push({
|
|
6593
|
+
category: "doc-structure",
|
|
6594
|
+
severity: "review",
|
|
6595
|
+
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.`,
|
|
6596
|
+
file: filePath
|
|
6597
|
+
});
|
|
6598
|
+
}
|
|
6599
|
+
if (forbiddenHeadings.length > 0) {
|
|
6600
|
+
diagnostics.push({
|
|
6601
|
+
category: "lane-drift",
|
|
6602
|
+
severity: "error",
|
|
6603
|
+
message: `Canonical ${lane} truth doc ${filePath} contains wrong-lane section(s): ${forbiddenHeadings.join(", ")}.`,
|
|
6604
|
+
file: filePath
|
|
6605
|
+
});
|
|
6606
|
+
}
|
|
6005
6607
|
}
|
|
6006
6608
|
return diagnostics;
|
|
6007
6609
|
};
|
|
@@ -6093,17 +6695,90 @@ var checkGeneratedSurfaces = async (rootDir, config) => {
|
|
|
6093
6695
|
import path10 from "path";
|
|
6094
6696
|
import micromatch7 from "micromatch";
|
|
6095
6697
|
|
|
6096
|
-
// src/repo-index/build.ts
|
|
6097
|
-
import fs18 from "fs/promises";
|
|
6098
|
-
import path8 from "path";
|
|
6099
|
-
|
|
6100
6698
|
// src/repo-index/file-tree.ts
|
|
6101
6699
|
import fs16 from "fs/promises";
|
|
6102
|
-
import
|
|
6700
|
+
import path7 from "path";
|
|
6103
6701
|
import { execa as execa2 } from "execa";
|
|
6104
6702
|
import fg5 from "fast-glob";
|
|
6105
|
-
import matter2 from "gray-matter";
|
|
6106
6703
|
import micromatch6 from "micromatch";
|
|
6704
|
+
|
|
6705
|
+
// src/truth/source-references.ts
|
|
6706
|
+
import path6 from "path";
|
|
6707
|
+
var repoRootPrefixes = [
|
|
6708
|
+
".codex/",
|
|
6709
|
+
".github/",
|
|
6710
|
+
".truthmark/",
|
|
6711
|
+
"docs/",
|
|
6712
|
+
"src/",
|
|
6713
|
+
"tests/"
|
|
6714
|
+
];
|
|
6715
|
+
var sourceReferencesHeadingPattern = /^##\s+Source References\s*$/imu;
|
|
6716
|
+
var nextSecondLevelHeadingPattern = /^##\s+/imu;
|
|
6717
|
+
var bulletReferencePattern = /^\s*[-*]\s+(.+?)\s*$/u;
|
|
6718
|
+
var markdownLinkPattern = /^\s*\[[^\]]+\]\(([^)]+)\)\s*$/u;
|
|
6719
|
+
var backtickPathPattern = /^\s*`([^`]+)`\s*$/u;
|
|
6720
|
+
var normalizeSourceReferencePath = (truthDocPath, referencePath) => {
|
|
6721
|
+
const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
|
|
6722
|
+
const isRepoRelative = repoRootPrefixes.some(
|
|
6723
|
+
(prefix) => strippedPath.startsWith(prefix)
|
|
6724
|
+
);
|
|
6725
|
+
if (!isRepoRelative && (strippedPath.startsWith(".") || !strippedPath.includes("/"))) {
|
|
6726
|
+
return path6.posix.normalize(
|
|
6727
|
+
path6.posix.join(path6.posix.dirname(truthDocPath), strippedPath)
|
|
6728
|
+
);
|
|
6729
|
+
}
|
|
6730
|
+
return path6.posix.normalize(strippedPath);
|
|
6731
|
+
};
|
|
6732
|
+
var normalizeReferenceText = (value) => {
|
|
6733
|
+
const trimmed = value.trim();
|
|
6734
|
+
const markdownLinkMatch = markdownLinkPattern.exec(trimmed);
|
|
6735
|
+
if (markdownLinkMatch) {
|
|
6736
|
+
return markdownLinkMatch[1]?.trim() ?? "";
|
|
6737
|
+
}
|
|
6738
|
+
const backtickMatch = backtickPathPattern.exec(trimmed);
|
|
6739
|
+
if (backtickMatch) {
|
|
6740
|
+
return backtickMatch[1]?.trim() ?? "";
|
|
6741
|
+
}
|
|
6742
|
+
return trimmed;
|
|
6743
|
+
};
|
|
6744
|
+
var parseSourceReferencesSection = (content) => {
|
|
6745
|
+
const headingMatch = sourceReferencesHeadingPattern.exec(content);
|
|
6746
|
+
if (!headingMatch || typeof headingMatch.index !== "number") {
|
|
6747
|
+
return [];
|
|
6748
|
+
}
|
|
6749
|
+
const sectionStart = headingMatch.index + headingMatch[0].length;
|
|
6750
|
+
const afterHeading = content.slice(sectionStart);
|
|
6751
|
+
const nextHeadingMatch = nextSecondLevelHeadingPattern.exec(afterHeading);
|
|
6752
|
+
const section = nextHeadingMatch ? afterHeading.slice(0, nextHeadingMatch.index) : afterHeading;
|
|
6753
|
+
const references = [];
|
|
6754
|
+
for (const line of section.split("\n")) {
|
|
6755
|
+
const bulletMatch = bulletReferencePattern.exec(line);
|
|
6756
|
+
if (!bulletMatch) {
|
|
6757
|
+
continue;
|
|
6758
|
+
}
|
|
6759
|
+
const reference = normalizeReferenceText(bulletMatch[1] ?? "");
|
|
6760
|
+
if (reference.length > 0 && !reference.startsWith("{{")) {
|
|
6761
|
+
references.push(reference);
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
return references;
|
|
6765
|
+
};
|
|
6766
|
+
var parseSourceReferences = (source, truthDocPath) => {
|
|
6767
|
+
const parsed = parseFrontmatter(source);
|
|
6768
|
+
const references = /* @__PURE__ */ new Set();
|
|
6769
|
+
const frontmatterSourceOfTruth = Array.isArray(parsed.data.source_of_truth) ? parsed.data.source_of_truth : [];
|
|
6770
|
+
for (const entry of frontmatterSourceOfTruth) {
|
|
6771
|
+
if (typeof entry === "string") {
|
|
6772
|
+
references.add(normalizeSourceReferencePath(truthDocPath, entry));
|
|
6773
|
+
}
|
|
6774
|
+
}
|
|
6775
|
+
for (const entry of parseSourceReferencesSection(parsed.content)) {
|
|
6776
|
+
references.add(normalizeSourceReferencePath(truthDocPath, entry));
|
|
6777
|
+
}
|
|
6778
|
+
return [...references].sort();
|
|
6779
|
+
};
|
|
6780
|
+
|
|
6781
|
+
// src/repo-index/file-tree.ts
|
|
6107
6782
|
var languageByExtension = /* @__PURE__ */ new Map([
|
|
6108
6783
|
[".ts", "typescript"],
|
|
6109
6784
|
[".tsx", "typescript"],
|
|
@@ -6117,12 +6792,8 @@ var languageByExtension = /* @__PURE__ */ new Map([
|
|
|
6117
6792
|
[".yaml", "yaml"],
|
|
6118
6793
|
[".toml", "toml"]
|
|
6119
6794
|
]);
|
|
6120
|
-
var sourceExtensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
6121
|
-
var isJavaScriptLikePath = (filePath) => {
|
|
6122
|
-
return sourceExtensions.has(path6.posix.extname(filePath));
|
|
6123
|
-
};
|
|
6124
6795
|
var isTestPath = (filePath) => {
|
|
6125
|
-
return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(
|
|
6796
|
+
return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path7.posix.basename(filePath));
|
|
6126
6797
|
};
|
|
6127
6798
|
var fileKind = (filePath, ignore) => {
|
|
6128
6799
|
const classification = classifyPath(filePath, ignore);
|
|
@@ -6148,12 +6819,14 @@ var fileKind = (filePath, ignore) => {
|
|
|
6148
6819
|
};
|
|
6149
6820
|
var targetHintsForTest = (filePath) => {
|
|
6150
6821
|
const hints = /* @__PURE__ */ new Set();
|
|
6151
|
-
const basename =
|
|
6822
|
+
const basename = path7.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
|
|
6152
6823
|
if (basename.length > 0) {
|
|
6153
6824
|
hints.add(basename);
|
|
6154
6825
|
}
|
|
6155
6826
|
const segments = filePath.split("/");
|
|
6156
|
-
const testRootIndex = segments.findIndex(
|
|
6827
|
+
const testRootIndex = segments.findIndex(
|
|
6828
|
+
(segment) => segment === "tests" || segment === "__tests__"
|
|
6829
|
+
);
|
|
6157
6830
|
if (testRootIndex >= 0) {
|
|
6158
6831
|
for (const segment of segments.slice(testRootIndex + 1, -1)) {
|
|
6159
6832
|
if (segment.length > 0) {
|
|
@@ -6165,6 +6838,9 @@ var targetHintsForTest = (filePath) => {
|
|
|
6165
6838
|
};
|
|
6166
6839
|
var defaultIgnore = [".git/**", "node_modules/**", "dist/**", "build/**"];
|
|
6167
6840
|
var normalizePath2 = (filePath) => filePath.replaceAll("\\", "/").replace(/^\.\/+/u, "");
|
|
6841
|
+
var isTruthDocumentKind4 = (value) => {
|
|
6842
|
+
return typeof value === "string" && TRUTH_DOCUMENT_KINDS.includes(value);
|
|
6843
|
+
};
|
|
6168
6844
|
var gitDiscoverableFiles = async (rootDir) => {
|
|
6169
6845
|
const result = await execa2(
|
|
6170
6846
|
"git",
|
|
@@ -6196,7 +6872,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6196
6872
|
for (const filePath of discoveredFiles.filter((entry) => !isIgnoredPath(entry, ignore)).sort()) {
|
|
6197
6873
|
let stat;
|
|
6198
6874
|
try {
|
|
6199
|
-
stat = await fs16.stat(
|
|
6875
|
+
stat = await fs16.stat(path7.join(rootDir, filePath));
|
|
6200
6876
|
} catch (error) {
|
|
6201
6877
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
6202
6878
|
continue;
|
|
@@ -6206,7 +6882,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6206
6882
|
if (!stat.isFile()) {
|
|
6207
6883
|
continue;
|
|
6208
6884
|
}
|
|
6209
|
-
const extension =
|
|
6885
|
+
const extension = path7.posix.extname(filePath);
|
|
6210
6886
|
const kind = fileKind(filePath, ignore);
|
|
6211
6887
|
files.push({
|
|
6212
6888
|
path: filePath,
|
|
@@ -6220,17 +6896,23 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6220
6896
|
});
|
|
6221
6897
|
}
|
|
6222
6898
|
if (kind === "doc") {
|
|
6223
|
-
const source = await fs16.readFile(
|
|
6224
|
-
const parsed =
|
|
6899
|
+
const source = await fs16.readFile(path7.join(rootDir, filePath), "utf8");
|
|
6900
|
+
const parsed = parseFrontmatter(source);
|
|
6225
6901
|
const markdown = parseMarkdownDocument(parsed.content);
|
|
6226
6902
|
const title = markdown.headings.find((heading) => heading.depth === 1)?.text ?? null;
|
|
6227
|
-
const sourceOfTruth =
|
|
6903
|
+
const sourceOfTruth = parseSourceReferences(source, filePath);
|
|
6904
|
+
const truthKind = typeof parsed.data.truth_kind === "string" ? parsed.data.truth_kind : null;
|
|
6905
|
+
const derivedTruthKind = isTruthDocumentKind4(truthKind) ? truthKind : null;
|
|
6228
6906
|
docs.push({
|
|
6229
6907
|
path: filePath,
|
|
6230
6908
|
title,
|
|
6231
|
-
docType: typeof parsed.data.doc_type === "string" ? parsed.data.doc_type : null,
|
|
6232
|
-
truthKind
|
|
6233
|
-
|
|
6909
|
+
docType: typeof parsed.data.doc_type === "string" ? parsed.data.doc_type : derivedTruthKind ? docTypeForTruthDocumentKind(derivedTruthKind) : null,
|
|
6910
|
+
truthKind,
|
|
6911
|
+
truthLane: typeof parsed.data.truth_lane === "string" ? parsed.data.truth_lane : derivedTruthKind ? laneForTruthDocumentKind(derivedTruthKind) : null,
|
|
6912
|
+
sourceOfTruth: sourceOfTruth.sort(),
|
|
6913
|
+
realizedBy: [],
|
|
6914
|
+
realizes: [],
|
|
6915
|
+
dependsOn: []
|
|
6234
6916
|
});
|
|
6235
6917
|
}
|
|
6236
6918
|
}
|
|
@@ -6243,7 +6925,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6243
6925
|
|
|
6244
6926
|
// src/repo-index/package-metadata.ts
|
|
6245
6927
|
import fs17 from "fs/promises";
|
|
6246
|
-
import
|
|
6928
|
+
import path8 from "path";
|
|
6247
6929
|
import fg6 from "fast-glob";
|
|
6248
6930
|
var packageManagerFor = async (rootDir, packageDir) => {
|
|
6249
6931
|
const lockfiles = [
|
|
@@ -6255,7 +6937,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
|
|
|
6255
6937
|
];
|
|
6256
6938
|
for (const [lockfile, manager] of lockfiles) {
|
|
6257
6939
|
try {
|
|
6258
|
-
await fs17.access(
|
|
6940
|
+
await fs17.access(path8.join(rootDir, packageDir, lockfile));
|
|
6259
6941
|
return manager;
|
|
6260
6942
|
} catch {
|
|
6261
6943
|
continue;
|
|
@@ -6272,8 +6954,8 @@ var discoverPackageMetadata = async (rootDir) => {
|
|
|
6272
6954
|
});
|
|
6273
6955
|
const packages = [];
|
|
6274
6956
|
for (const packageFile of packageFiles.sort()) {
|
|
6275
|
-
const packageDir =
|
|
6276
|
-
const raw = JSON.parse(await fs17.readFile(
|
|
6957
|
+
const packageDir = path8.posix.dirname(packageFile) === "." ? "" : path8.posix.dirname(packageFile);
|
|
6958
|
+
const raw = JSON.parse(await fs17.readFile(path8.join(rootDir, packageFile), "utf8"));
|
|
6277
6959
|
const scripts = raw.scripts && typeof raw.scripts === "object" ? Object.keys(raw.scripts).sort() : [];
|
|
6278
6960
|
packages.push({
|
|
6279
6961
|
path: packageFile,
|
|
@@ -6288,6 +6970,21 @@ var discoverPackageMetadata = async (rootDir) => {
|
|
|
6288
6970
|
};
|
|
6289
6971
|
|
|
6290
6972
|
// src/repo-index/route-map.ts
|
|
6973
|
+
var mergedEntryMap = (entries) => {
|
|
6974
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6975
|
+
for (const entry of entries) {
|
|
6976
|
+
const existingEntry = byPath.get(entry.path);
|
|
6977
|
+
if (existingEntry && existingEntry.kind === entry.kind && existingEntry.lane === entry.lane) {
|
|
6978
|
+
byPath.set(
|
|
6979
|
+
entry.path,
|
|
6980
|
+
mergeTruthDocumentEntryRelationships(existingEntry, entry)
|
|
6981
|
+
);
|
|
6982
|
+
} else if (!existingEntry) {
|
|
6983
|
+
byPath.set(entry.path, entry);
|
|
6984
|
+
}
|
|
6985
|
+
}
|
|
6986
|
+
return byPath;
|
|
6987
|
+
};
|
|
6291
6988
|
var buildRouteMap = async (rootDir) => {
|
|
6292
6989
|
const loadResult = await loadConfig(rootDir);
|
|
6293
6990
|
if (!loadResult.config) {
|
|
@@ -6300,8 +6997,12 @@ var buildRouteMap = async (rootDir) => {
|
|
|
6300
6997
|
const routing = await resolveAreaRouting(rootDir, {
|
|
6301
6998
|
rootIndex: loadResult.config.truthmark.paths.routesIndex,
|
|
6302
6999
|
areaFilesRoot: loadResult.config.truthmark.paths.routeAreasRoot,
|
|
6303
|
-
|
|
7000
|
+
productTruthRoot: resolveProductTruthRoot(loadResult.config),
|
|
7001
|
+
engineeringTruthRoot: resolveEngineeringTruthRoot(loadResult.config)
|
|
6304
7002
|
});
|
|
7003
|
+
const mergedTruthDocumentEntries = mergedEntryMap(
|
|
7004
|
+
routing.areas.flatMap((area) => area.truthDocumentEntries)
|
|
7005
|
+
);
|
|
6305
7006
|
return {
|
|
6306
7007
|
schemaVersion: "route-map/v0",
|
|
6307
7008
|
routes: routing.areas.map((area) => ({
|
|
@@ -6312,102 +7013,20 @@ var buildRouteMap = async (rootDir) => {
|
|
|
6312
7013
|
parentName: area.parentName,
|
|
6313
7014
|
codeSurface: [...area.codeSurface].sort(),
|
|
6314
7015
|
truthDocs: [...area.truthDocuments].sort(),
|
|
7016
|
+
truthDocumentEntries: [
|
|
7017
|
+
...new Map(
|
|
7018
|
+
area.truthDocumentEntries.map((entry) => [
|
|
7019
|
+
entry.path,
|
|
7020
|
+
mergedTruthDocumentEntries.get(entry.path) ?? entry
|
|
7021
|
+
])
|
|
7022
|
+
).values()
|
|
7023
|
+
].sort((left, right) => left.path.localeCompare(right.path)),
|
|
6315
7024
|
updateTruthWhen: [...area.updateTruthWhen]
|
|
6316
7025
|
})).sort((left, right) => left.key.localeCompare(right.key)),
|
|
6317
7026
|
diagnostics: routing.diagnostics
|
|
6318
7027
|
};
|
|
6319
7028
|
};
|
|
6320
7029
|
|
|
6321
|
-
// src/repo-index/typescript-symbols.ts
|
|
6322
|
-
import ts from "typescript";
|
|
6323
|
-
var sortStrings = (values) => [...new Set(values)].sort();
|
|
6324
|
-
var hasExportModifier = (node) => {
|
|
6325
|
-
return Boolean(
|
|
6326
|
-
ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
|
|
6327
|
-
);
|
|
6328
|
-
};
|
|
6329
|
-
var declarationName = (node) => {
|
|
6330
|
-
if (!node.name || !ts.isIdentifier(node.name)) {
|
|
6331
|
-
return null;
|
|
6332
|
-
}
|
|
6333
|
-
return node.name.text;
|
|
6334
|
-
};
|
|
6335
|
-
var addExport = (exports, publicSymbols, path13, name, kind) => {
|
|
6336
|
-
if (!name) {
|
|
6337
|
-
return;
|
|
6338
|
-
}
|
|
6339
|
-
const entry = { path: path13, name, kind };
|
|
6340
|
-
exports.push(entry);
|
|
6341
|
-
publicSymbols.push(entry);
|
|
6342
|
-
};
|
|
6343
|
-
var analyzeTypeScriptSource = (path13, source) => {
|
|
6344
|
-
const sourceFile = ts.createSourceFile(path13, source, ts.ScriptTarget.Latest, true);
|
|
6345
|
-
const imports = [];
|
|
6346
|
-
const exports = [];
|
|
6347
|
-
const publicSymbols = [];
|
|
6348
|
-
for (const statement of sourceFile.statements) {
|
|
6349
|
-
if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
6350
|
-
const imported = [];
|
|
6351
|
-
const clause = statement.importClause;
|
|
6352
|
-
if (clause?.name) {
|
|
6353
|
-
imported.push("default");
|
|
6354
|
-
}
|
|
6355
|
-
if (clause?.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
|
|
6356
|
-
imported.push("*");
|
|
6357
|
-
}
|
|
6358
|
-
if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
6359
|
-
for (const element of clause.namedBindings.elements) {
|
|
6360
|
-
imported.push(element.propertyName?.text ?? element.name.text);
|
|
6361
|
-
}
|
|
6362
|
-
}
|
|
6363
|
-
imports.push({
|
|
6364
|
-
from: path13,
|
|
6365
|
-
specifier: statement.moduleSpecifier.text,
|
|
6366
|
-
imported: sortStrings(imported)
|
|
6367
|
-
});
|
|
6368
|
-
continue;
|
|
6369
|
-
}
|
|
6370
|
-
if (ts.isExportDeclaration(statement)) {
|
|
6371
|
-
if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
6372
|
-
for (const element of statement.exportClause.elements) {
|
|
6373
|
-
addExport(exports, publicSymbols, path13, element.name.text, "re-export");
|
|
6374
|
-
}
|
|
6375
|
-
}
|
|
6376
|
-
continue;
|
|
6377
|
-
}
|
|
6378
|
-
if (ts.isFunctionDeclaration(statement) && hasExportModifier(statement)) {
|
|
6379
|
-
addExport(exports, publicSymbols, path13, declarationName(statement), "function");
|
|
6380
|
-
continue;
|
|
6381
|
-
}
|
|
6382
|
-
if (ts.isClassDeclaration(statement) && hasExportModifier(statement)) {
|
|
6383
|
-
addExport(exports, publicSymbols, path13, declarationName(statement), "class");
|
|
6384
|
-
continue;
|
|
6385
|
-
}
|
|
6386
|
-
if (ts.isInterfaceDeclaration(statement) && hasExportModifier(statement)) {
|
|
6387
|
-
addExport(exports, publicSymbols, path13, declarationName(statement), "interface");
|
|
6388
|
-
continue;
|
|
6389
|
-
}
|
|
6390
|
-
if (ts.isTypeAliasDeclaration(statement) && hasExportModifier(statement)) {
|
|
6391
|
-
addExport(exports, publicSymbols, path13, declarationName(statement), "type");
|
|
6392
|
-
continue;
|
|
6393
|
-
}
|
|
6394
|
-
if (ts.isEnumDeclaration(statement) && hasExportModifier(statement)) {
|
|
6395
|
-
addExport(exports, publicSymbols, path13, declarationName(statement), "enum");
|
|
6396
|
-
continue;
|
|
6397
|
-
}
|
|
6398
|
-
if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
|
|
6399
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
6400
|
-
addExport(exports, publicSymbols, path13, declarationName(declaration), "const");
|
|
6401
|
-
}
|
|
6402
|
-
}
|
|
6403
|
-
}
|
|
6404
|
-
return {
|
|
6405
|
-
imports: imports.sort((left, right) => left.specifier.localeCompare(right.specifier)),
|
|
6406
|
-
exports: exports.sort((left, right) => left.name.localeCompare(right.name)),
|
|
6407
|
-
publicSymbols: publicSymbols.sort((left, right) => left.name.localeCompare(right.name))
|
|
6408
|
-
};
|
|
6409
|
-
};
|
|
6410
|
-
|
|
6411
7030
|
// src/repo-index/build.ts
|
|
6412
7031
|
var buildRepoIndex = async (cwd) => {
|
|
6413
7032
|
const repository = await getGitRepository(cwd);
|
|
@@ -6420,20 +7039,7 @@ var buildRepoIndex = async (cwd) => {
|
|
|
6420
7039
|
discoverRepoFiles(rootDir, ignore),
|
|
6421
7040
|
buildRouteMap(rootDir)
|
|
6422
7041
|
]);
|
|
6423
|
-
const imports = [];
|
|
6424
|
-
const exports = [];
|
|
6425
|
-
const publicSymbols = [];
|
|
6426
7042
|
diagnostics.push(...routeMap.diagnostics);
|
|
6427
|
-
for (const file of fileTree.files) {
|
|
6428
|
-
if (!isJavaScriptLikePath(file.path)) {
|
|
6429
|
-
continue;
|
|
6430
|
-
}
|
|
6431
|
-
const source = await fs18.readFile(path8.join(rootDir, file.path), "utf8");
|
|
6432
|
-
const analysis = analyzeTypeScriptSource(file.path, source);
|
|
6433
|
-
imports.push(...analysis.imports);
|
|
6434
|
-
exports.push(...analysis.exports);
|
|
6435
|
-
publicSymbols.push(...analysis.publicSymbols);
|
|
6436
|
-
}
|
|
6437
7043
|
return {
|
|
6438
7044
|
schemaVersion: "repo-index/v0",
|
|
6439
7045
|
repository: {
|
|
@@ -6445,11 +7051,6 @@ var buildRepoIndex = async (cwd) => {
|
|
|
6445
7051
|
files: fileTree.files,
|
|
6446
7052
|
docs: fileTree.docs,
|
|
6447
7053
|
tests: fileTree.tests,
|
|
6448
|
-
imports: imports.sort((left, right) => `${left.from}:${left.specifier}`.localeCompare(`${right.from}:${right.specifier}`)),
|
|
6449
|
-
exports: exports.sort((left, right) => `${left.path}:${left.name}`.localeCompare(`${right.path}:${right.name}`)),
|
|
6450
|
-
publicSymbols: publicSymbols.sort(
|
|
6451
|
-
(left, right) => `${left.path}:${left.name}`.localeCompare(`${right.path}:${right.name}`)
|
|
6452
|
-
),
|
|
6453
7054
|
routeMap,
|
|
6454
7055
|
diagnostics
|
|
6455
7056
|
};
|
|
@@ -6459,7 +7060,7 @@ var buildRepoIndex = async (cwd) => {
|
|
|
6459
7060
|
import { execa as execa4 } from "execa";
|
|
6460
7061
|
|
|
6461
7062
|
// src/git/changes.ts
|
|
6462
|
-
import
|
|
7063
|
+
import fs18 from "fs/promises";
|
|
6463
7064
|
import path9 from "path";
|
|
6464
7065
|
import { execa as execa3 } from "execa";
|
|
6465
7066
|
var normalizePath3 = (filePath) => {
|
|
@@ -6471,7 +7072,7 @@ var listChangedPaths = async (cwd, args) => {
|
|
|
6471
7072
|
};
|
|
6472
7073
|
var pathExists4 = async (filePath) => {
|
|
6473
7074
|
try {
|
|
6474
|
-
await
|
|
7075
|
+
await fs18.access(filePath);
|
|
6475
7076
|
return true;
|
|
6476
7077
|
} catch {
|
|
6477
7078
|
return false;
|
|
@@ -6599,17 +7200,9 @@ var getChangedFiles = async (cwd, base) => {
|
|
|
6599
7200
|
diagnostics
|
|
6600
7201
|
};
|
|
6601
7202
|
};
|
|
6602
|
-
var readBaseFile = async (cwd, base, filePath) => {
|
|
6603
|
-
const repository = await getGitRepository(cwd);
|
|
6604
|
-
const result = await execa4("git", ["show", `${base}:${filePath}`], {
|
|
6605
|
-
cwd: repository.worktreePath,
|
|
6606
|
-
reject: false
|
|
6607
|
-
});
|
|
6608
|
-
return (result.exitCode ?? 1) === 0 ? result.stdout : null;
|
|
6609
|
-
};
|
|
6610
7203
|
|
|
6611
7204
|
// src/impact/build.ts
|
|
6612
|
-
var
|
|
7205
|
+
var uniqueSorted2 = (values) => [...new Set(values)].sort();
|
|
6613
7206
|
var routeMatchesFile = (route, filePath) => {
|
|
6614
7207
|
return route.codeSurface.some((pattern) => micromatch7.isMatch(filePath, pattern));
|
|
6615
7208
|
};
|
|
@@ -6624,65 +7217,15 @@ var toImpactRoute = (route) => ({
|
|
|
6624
7217
|
truthDocs: [...route.truthDocs].sort(),
|
|
6625
7218
|
codeSurface: [...route.codeSurface].sort()
|
|
6626
7219
|
});
|
|
6627
|
-
var changedPathSet = (changedFiles) => {
|
|
6628
|
-
return new Set(
|
|
6629
|
-
changedFiles.flatMap((file) => [file.path, ...file.previousPath ? [file.previousPath] : []])
|
|
6630
|
-
);
|
|
6631
|
-
};
|
|
6632
7220
|
var changedFilePaths = (changedFile) => {
|
|
6633
7221
|
return [changedFile.path, ...changedFile.previousPath ? [changedFile.previousPath] : []];
|
|
6634
7222
|
};
|
|
6635
|
-
var resolveImportPath = (importEdge) => {
|
|
6636
|
-
if (!importEdge.specifier.startsWith(".")) {
|
|
6637
|
-
return null;
|
|
6638
|
-
}
|
|
6639
|
-
const basePath = path10.posix.normalize(path10.posix.join(path10.posix.dirname(importEdge.from), importEdge.specifier));
|
|
6640
|
-
const withoutExtension = basePath.replace(/\.[cm]?[jt]sx?$/u, "");
|
|
6641
|
-
return withoutExtension;
|
|
6642
|
-
};
|
|
6643
|
-
var importTargetsChangedFile = (importEdge, changedPath) => {
|
|
6644
|
-
const resolved = resolveImportPath(importEdge);
|
|
6645
|
-
if (!resolved) {
|
|
6646
|
-
return false;
|
|
6647
|
-
}
|
|
6648
|
-
return changedPath.replace(/\.[cm]?[jt]sx?$/u, "") === resolved;
|
|
6649
|
-
};
|
|
6650
7223
|
var pathSegments = (filePath) => filePath.split("/").filter(Boolean);
|
|
6651
7224
|
var testHintMatchesChangedFile = (hints, changedPath) => {
|
|
6652
7225
|
const changedBaseName = path10.posix.basename(changedPath);
|
|
6653
7226
|
const changedSegments = pathSegments(changedPath);
|
|
6654
7227
|
return hints.some((hint) => changedBaseName.startsWith(hint) || changedSegments.includes(hint));
|
|
6655
7228
|
};
|
|
6656
|
-
var changedSymbolsFor = async (cwd, base, basePath, currentPath, currentExports) => {
|
|
6657
|
-
if (!isJavaScriptLikePath(basePath) && !isJavaScriptLikePath(currentPath)) {
|
|
6658
|
-
return [];
|
|
6659
|
-
}
|
|
6660
|
-
const baseSource = await readBaseFile(cwd, base, basePath);
|
|
6661
|
-
const baseExports = baseSource ? analyzeTypeScriptSource(basePath, baseSource).exports : [];
|
|
6662
|
-
const currentByName = new Map(currentExports.map((entry) => [entry.name, entry]));
|
|
6663
|
-
const baseByName = new Map(baseExports.map((entry) => [entry.name, entry]));
|
|
6664
|
-
const changes = [];
|
|
6665
|
-
if (basePath !== currentPath) {
|
|
6666
|
-
for (const entry of currentExports) {
|
|
6667
|
-
changes.push({ path: currentPath, name: entry.name, kind: entry.kind, change: "added" });
|
|
6668
|
-
}
|
|
6669
|
-
for (const entry of baseExports) {
|
|
6670
|
-
changes.push({ path: basePath, name: entry.name, kind: entry.kind, change: "removed" });
|
|
6671
|
-
}
|
|
6672
|
-
return changes;
|
|
6673
|
-
}
|
|
6674
|
-
for (const [name, entry] of currentByName) {
|
|
6675
|
-
if (!baseByName.has(name)) {
|
|
6676
|
-
changes.push({ path: currentPath, name, kind: entry.kind, change: "added" });
|
|
6677
|
-
}
|
|
6678
|
-
}
|
|
6679
|
-
for (const [name, entry] of baseByName) {
|
|
6680
|
-
if (!currentByName.has(name)) {
|
|
6681
|
-
changes.push({ path: basePath, name, kind: entry.kind, change: "removed" });
|
|
6682
|
-
}
|
|
6683
|
-
}
|
|
6684
|
-
return changes;
|
|
6685
|
-
};
|
|
6686
7229
|
var buildImpactSet = async (cwd, options) => {
|
|
6687
7230
|
const repository = await getGitRepository(cwd);
|
|
6688
7231
|
const rootDir = repository.worktreePath;
|
|
@@ -6697,7 +7240,6 @@ var buildImpactSet = async (cwd, options) => {
|
|
|
6697
7240
|
const affectedRoutes = /* @__PURE__ */ new Map();
|
|
6698
7241
|
const affectedTruthDocs = [];
|
|
6699
7242
|
const affectedTests = [];
|
|
6700
|
-
const changedPublicSymbols = [];
|
|
6701
7243
|
const knownTestPaths = new Set(repoIndex.tests.map((test) => test.path));
|
|
6702
7244
|
for (const changedFile of changedFiles) {
|
|
6703
7245
|
const routeCandidatePaths = changedFilePaths(changedFile);
|
|
@@ -6730,60 +7272,15 @@ var buildImpactSet = async (cwd, options) => {
|
|
|
6730
7272
|
file: changedFile.path
|
|
6731
7273
|
});
|
|
6732
7274
|
}
|
|
6733
|
-
const currentExports = repoIndex.exports.filter((entry) => entry.path === changedFile.path);
|
|
6734
|
-
changedPublicSymbols.push(
|
|
6735
|
-
...await changedSymbolsFor(
|
|
6736
|
-
rootDir,
|
|
6737
|
-
options.base,
|
|
6738
|
-
changedFile.previousPath ?? changedFile.path,
|
|
6739
|
-
changedFile.path,
|
|
6740
|
-
currentExports
|
|
6741
|
-
)
|
|
6742
|
-
);
|
|
6743
|
-
}
|
|
6744
|
-
const uniqueAffectedTruthDocs = uniqueSorted(affectedTruthDocs);
|
|
6745
|
-
const changedPaths = changedPathSet(changedFiles);
|
|
6746
|
-
for (const symbol of changedPublicSymbols) {
|
|
6747
|
-
if (uniqueAffectedTruthDocs.length === 0) {
|
|
6748
|
-
diagnostics.push({
|
|
6749
|
-
category: "impact",
|
|
6750
|
-
severity: "review",
|
|
6751
|
-
message: `Changed public symbol ${symbol.name} in ${symbol.path} has no affected truth document.`,
|
|
6752
|
-
file: symbol.path,
|
|
6753
|
-
data: {
|
|
6754
|
-
symbol: symbol.name,
|
|
6755
|
-
change: symbol.change
|
|
6756
|
-
}
|
|
6757
|
-
});
|
|
6758
|
-
continue;
|
|
6759
|
-
}
|
|
6760
|
-
if (!uniqueAffectedTruthDocs.some((truthDoc) => changedPaths.has(truthDoc))) {
|
|
6761
|
-
diagnostics.push({
|
|
6762
|
-
category: "impact",
|
|
6763
|
-
severity: "review",
|
|
6764
|
-
message: `Changed public symbol ${symbol.name} in ${symbol.path} has affected truth docs but none were changed in this impact set.`,
|
|
6765
|
-
file: symbol.path,
|
|
6766
|
-
data: {
|
|
6767
|
-
symbol: symbol.name,
|
|
6768
|
-
change: symbol.change,
|
|
6769
|
-
affectedTruthDocs: uniqueAffectedTruthDocs
|
|
6770
|
-
}
|
|
6771
|
-
});
|
|
6772
|
-
}
|
|
6773
7275
|
}
|
|
7276
|
+
const uniqueAffectedTruthDocs = uniqueSorted2(affectedTruthDocs);
|
|
6774
7277
|
for (const test of repoIndex.tests) {
|
|
6775
|
-
const testImports = repoIndex.imports.filter((edge) => edge.from === test.path);
|
|
6776
|
-
const importsChangedFile = changedFiles.some(
|
|
6777
|
-
(changedFile) => changedFilePaths(changedFile).some(
|
|
6778
|
-
(filePath) => testImports.some((importEdge) => importTargetsChangedFile(importEdge, filePath))
|
|
6779
|
-
)
|
|
6780
|
-
);
|
|
6781
7278
|
const hintMatchesChangedFile = changedFiles.some(
|
|
6782
7279
|
(changedFile) => changedFilePaths(changedFile).some(
|
|
6783
7280
|
(filePath) => testHintMatchesChangedFile(test.targetHints, filePath)
|
|
6784
7281
|
)
|
|
6785
7282
|
);
|
|
6786
|
-
if (
|
|
7283
|
+
if (hintMatchesChangedFile) {
|
|
6787
7284
|
affectedTests.push(test.path);
|
|
6788
7285
|
}
|
|
6789
7286
|
}
|
|
@@ -6796,10 +7293,7 @@ var buildImpactSet = async (cwd, options) => {
|
|
|
6796
7293
|
(left, right) => left.key.localeCompare(right.key)
|
|
6797
7294
|
),
|
|
6798
7295
|
affectedTruthDocs: uniqueAffectedTruthDocs,
|
|
6799
|
-
affectedTests:
|
|
6800
|
-
changedPublicSymbols: changedPublicSymbols.sort(
|
|
6801
|
-
(left, right) => `${left.path}:${left.name}:${left.change}`.localeCompare(`${right.path}:${right.name}:${right.change}`)
|
|
6802
|
-
),
|
|
7296
|
+
affectedTests: uniqueSorted2(affectedTests),
|
|
6803
7297
|
diagnostics
|
|
6804
7298
|
};
|
|
6805
7299
|
};
|
|
@@ -6825,32 +7319,22 @@ var checkFreshness = async (rootDir, _config, _truthDocumentPaths, base) => {
|
|
|
6825
7319
|
};
|
|
6826
7320
|
|
|
6827
7321
|
// src/evidence/validate.ts
|
|
6828
|
-
import
|
|
7322
|
+
import fs20 from "fs/promises";
|
|
6829
7323
|
import fg7 from "fast-glob";
|
|
6830
7324
|
|
|
6831
7325
|
// src/evidence/parse.ts
|
|
6832
|
-
import
|
|
7326
|
+
import fs19 from "fs/promises";
|
|
6833
7327
|
import path11 from "path";
|
|
6834
|
-
import
|
|
6835
|
-
import { parse as parse3 } from "yaml";
|
|
7328
|
+
import { parse as parse4 } from "yaml";
|
|
6836
7329
|
var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
|
|
6837
7330
|
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
7331
|
var toEvidenceReference = (truthDocPath, raw) => {
|
|
6848
7332
|
if (!raw || typeof raw !== "object" || !("path" in raw) || typeof raw.path !== "string") {
|
|
6849
7333
|
return null;
|
|
6850
7334
|
}
|
|
6851
7335
|
return {
|
|
6852
7336
|
truthDocPath,
|
|
6853
|
-
path:
|
|
7337
|
+
path: normalizeSourceReferencePath(truthDocPath, raw.path),
|
|
6854
7338
|
symbol: "symbol" in raw && typeof raw.symbol === "string" ? raw.symbol : void 0,
|
|
6855
7339
|
startLine: "start_line" in raw && typeof raw.start_line === "number" ? raw.start_line : void 0,
|
|
6856
7340
|
endLine: "end_line" in raw && typeof raw.end_line === "number" ? raw.end_line : void 0,
|
|
@@ -6859,18 +7343,14 @@ var toEvidenceReference = (truthDocPath, raw) => {
|
|
|
6859
7343
|
};
|
|
6860
7344
|
};
|
|
6861
7345
|
var parseEvidenceReferences = async (rootDir, truthDocPath) => {
|
|
6862
|
-
const source = await
|
|
6863
|
-
const parsed =
|
|
7346
|
+
const source = await fs19.readFile(path11.join(rootDir, truthDocPath), "utf8");
|
|
7347
|
+
const parsed = parseFrontmatter(source);
|
|
6864
7348
|
const references = [];
|
|
6865
|
-
const
|
|
6866
|
-
for (const entry of sourceOfTruth) {
|
|
6867
|
-
if (typeof entry !== "string") {
|
|
6868
|
-
continue;
|
|
6869
|
-
}
|
|
7349
|
+
for (const entry of parseSourceReferences(source, truthDocPath)) {
|
|
6870
7350
|
references.push({
|
|
6871
7351
|
truthDocPath,
|
|
6872
|
-
path:
|
|
6873
|
-
source: "
|
|
7352
|
+
path: entry,
|
|
7353
|
+
source: "source-references"
|
|
6874
7354
|
});
|
|
6875
7355
|
}
|
|
6876
7356
|
for (const match of parsed.content.matchAll(yamlFencePattern)) {
|
|
@@ -6878,7 +7358,7 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
|
|
|
6878
7358
|
if (!topLevelEvidenceMarkerPattern.test(yamlBlock)) {
|
|
6879
7359
|
continue;
|
|
6880
7360
|
}
|
|
6881
|
-
const block =
|
|
7361
|
+
const block = parse4(yamlBlock);
|
|
6882
7362
|
const rawEvidence = block && typeof block === "object" && "evidence" in block ? block.evidence : null;
|
|
6883
7363
|
if (!Array.isArray(rawEvidence)) {
|
|
6884
7364
|
continue;
|
|
@@ -6896,7 +7376,7 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
|
|
|
6896
7376
|
// src/evidence/validate.ts
|
|
6897
7377
|
var pathExists5 = async (filePath) => {
|
|
6898
7378
|
try {
|
|
6899
|
-
await
|
|
7379
|
+
await fs20.access(filePath);
|
|
6900
7380
|
return true;
|
|
6901
7381
|
} catch {
|
|
6902
7382
|
return false;
|
|
@@ -6934,15 +7414,6 @@ var validateGlob = async (rootDir, reference) => {
|
|
|
6934
7414
|
});
|
|
6935
7415
|
return matches.length > 0 ? null : diagnosticFor(reference, `Referenced file pattern ${reference.path} does not match any file.`);
|
|
6936
7416
|
};
|
|
6937
|
-
var validateSymbol = async (rootDir, reference) => {
|
|
6938
|
-
if (!reference.symbol || !isJavaScriptLikePath(reference.path)) {
|
|
6939
|
-
return null;
|
|
6940
|
-
}
|
|
6941
|
-
const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
|
|
6942
|
-
const analysis = analyzeTypeScriptSource(reference.path, source);
|
|
6943
|
-
const hasSymbol = analysis.publicSymbols.some((symbol) => symbol.name === reference.symbol);
|
|
6944
|
-
return hasSymbol ? null : diagnosticFor(reference, `Evidence symbol ${reference.symbol} was not found in ${reference.path}.`);
|
|
6945
|
-
};
|
|
6946
7417
|
var validateHash = async (rootDir, reference) => {
|
|
6947
7418
|
if (!reference.contentHash) {
|
|
6948
7419
|
return null;
|
|
@@ -6950,7 +7421,7 @@ var validateHash = async (rootDir, reference) => {
|
|
|
6950
7421
|
if (!reference.contentHash.startsWith("sha256:")) {
|
|
6951
7422
|
return diagnosticFor(reference, `Evidence hash for ${reference.path} must use sha256:.`);
|
|
6952
7423
|
}
|
|
6953
|
-
const source = await
|
|
7424
|
+
const source = await fs20.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
|
|
6954
7425
|
const lines = source.split("\n");
|
|
6955
7426
|
const startLine = reference.startLine ?? 1;
|
|
6956
7427
|
const endLine = reference.endLine ?? lines.length;
|
|
@@ -6964,7 +7435,7 @@ var validateLineSpan = async (rootDir, reference) => {
|
|
|
6964
7435
|
if (reference.startLine === void 0 && reference.endLine === void 0) {
|
|
6965
7436
|
return null;
|
|
6966
7437
|
}
|
|
6967
|
-
const source = await
|
|
7438
|
+
const source = await fs20.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
|
|
6968
7439
|
const lines = source.split("\n");
|
|
6969
7440
|
const startLine = reference.startLine ?? 1;
|
|
6970
7441
|
const endLine = reference.endLine ?? lines.length;
|
|
@@ -6983,10 +7454,6 @@ var validateReference = async (rootDir, reference) => {
|
|
|
6983
7454
|
diagnostics.push(diagnosticFor(reference, `Referenced file ${reference.path} does not exist.`));
|
|
6984
7455
|
return diagnostics;
|
|
6985
7456
|
}
|
|
6986
|
-
const symbolDiagnostic = await validateSymbol(rootDir, reference);
|
|
6987
|
-
if (symbolDiagnostic) {
|
|
6988
|
-
diagnostics.push(symbolDiagnostic);
|
|
6989
|
-
}
|
|
6990
7457
|
const lineSpanDiagnostic = await validateLineSpan(rootDir, reference);
|
|
6991
7458
|
if (lineSpanDiagnostic) {
|
|
6992
7459
|
diagnostics.push(lineSpanDiagnostic);
|
|
@@ -7243,190 +7710,6 @@ var runCheck = async (cwd, options = {}) => {
|
|
|
7243
7710
|
};
|
|
7244
7711
|
};
|
|
7245
7712
|
|
|
7246
|
-
// src/context-pack/build.ts
|
|
7247
|
-
import fs22 from "fs/promises";
|
|
7248
|
-
import path12 from "path";
|
|
7249
|
-
import fg8 from "fast-glob";
|
|
7250
|
-
var uniqueSorted2 = (values) => [...new Set(values)].sort();
|
|
7251
|
-
var repoRootPrefixes2 = [".codex/", ".github/", ".truthmark/", "docs/", "src/", "tests/"];
|
|
7252
|
-
var isGlobReference2 = (referencePath) => /[*?[\]{}()]/u.test(referencePath);
|
|
7253
|
-
var normalizeDocReferencePath = (docPath, referencePath) => {
|
|
7254
|
-
const strippedPath = referencePath.split("#")[0]?.trim() ?? "";
|
|
7255
|
-
if (strippedPath.length === 0 || strippedPath.startsWith("/")) {
|
|
7256
|
-
return null;
|
|
7257
|
-
}
|
|
7258
|
-
const isRepoRelative = repoRootPrefixes2.some((prefix) => strippedPath.startsWith(prefix));
|
|
7259
|
-
const normalized = isRepoRelative ? path12.posix.normalize(strippedPath) : path12.posix.normalize(path12.posix.join(path12.posix.dirname(docPath), strippedPath));
|
|
7260
|
-
return normalized === ".." || normalized.startsWith("../") ? null : normalized;
|
|
7261
|
-
};
|
|
7262
|
-
var readIfExists = async (rootDir, filePath) => {
|
|
7263
|
-
try {
|
|
7264
|
-
return await fs22.readFile(path12.join(rootDir, filePath), "utf8");
|
|
7265
|
-
} catch {
|
|
7266
|
-
return null;
|
|
7267
|
-
}
|
|
7268
|
-
};
|
|
7269
|
-
var boundContent = (content) => {
|
|
7270
|
-
const lines = content.split("\n");
|
|
7271
|
-
if (lines.length <= 200) {
|
|
7272
|
-
return { content, truncated: false };
|
|
7273
|
-
}
|
|
7274
|
-
return {
|
|
7275
|
-
content: [...lines.slice(0, 80), "...", ...lines.slice(-40)].join("\n"),
|
|
7276
|
-
truncated: true
|
|
7277
|
-
};
|
|
7278
|
-
};
|
|
7279
|
-
var boundedContent = (filePath, content, warnings) => {
|
|
7280
|
-
const bounded = boundContent(content);
|
|
7281
|
-
if (!bounded.truncated) {
|
|
7282
|
-
return { path: filePath, content, truncated: false };
|
|
7283
|
-
}
|
|
7284
|
-
warnings.push({
|
|
7285
|
-
category: "context-pack",
|
|
7286
|
-
severity: "review",
|
|
7287
|
-
message: `Context source file ${filePath} was truncated to fit ContextPack v0 bounds.`,
|
|
7288
|
-
file: filePath
|
|
7289
|
-
});
|
|
7290
|
-
return {
|
|
7291
|
-
path: filePath,
|
|
7292
|
-
content: bounded.content,
|
|
7293
|
-
truncated: true
|
|
7294
|
-
};
|
|
7295
|
-
};
|
|
7296
|
-
var documentsFor = async (rootDir, paths, warnings) => {
|
|
7297
|
-
const documents = [];
|
|
7298
|
-
for (const filePath of uniqueSorted2(paths)) {
|
|
7299
|
-
const content = await readIfExists(rootDir, filePath);
|
|
7300
|
-
if (content !== null) {
|
|
7301
|
-
const bounded = boundContent(content);
|
|
7302
|
-
if (bounded.truncated) {
|
|
7303
|
-
warnings.push({
|
|
7304
|
-
category: "context-pack",
|
|
7305
|
-
severity: "review",
|
|
7306
|
-
message: `Context truth doc ${filePath} was truncated to fit ContextPack v0 bounds.`,
|
|
7307
|
-
file: filePath
|
|
7308
|
-
});
|
|
7309
|
-
}
|
|
7310
|
-
documents.push({ path: filePath, content: bounded.content, truncated: bounded.truncated });
|
|
7311
|
-
}
|
|
7312
|
-
}
|
|
7313
|
-
return documents;
|
|
7314
|
-
};
|
|
7315
|
-
var sourceFilesFor = async (rootDir, paths, warnings) => {
|
|
7316
|
-
const sourceFiles = [];
|
|
7317
|
-
for (const filePath of uniqueSorted2(paths)) {
|
|
7318
|
-
const content = await readIfExists(rootDir, filePath);
|
|
7319
|
-
if (content !== null) {
|
|
7320
|
-
sourceFiles.push(boundedContent(filePath, content, warnings));
|
|
7321
|
-
}
|
|
7322
|
-
}
|
|
7323
|
-
return sourceFiles;
|
|
7324
|
-
};
|
|
7325
|
-
var sourceOfTruthPathsFor = async (rootDir, docs, truthDocPaths) => {
|
|
7326
|
-
const selectedTruthDocs = new Set(truthDocPaths);
|
|
7327
|
-
const sourcePaths = [];
|
|
7328
|
-
for (const doc of docs) {
|
|
7329
|
-
if (!selectedTruthDocs.has(doc.path)) {
|
|
7330
|
-
continue;
|
|
7331
|
-
}
|
|
7332
|
-
for (const referencePath of doc.sourceOfTruth) {
|
|
7333
|
-
const normalizedPath = normalizeDocReferencePath(doc.path, referencePath);
|
|
7334
|
-
if (!normalizedPath) {
|
|
7335
|
-
continue;
|
|
7336
|
-
}
|
|
7337
|
-
if (isGlobReference2(normalizedPath)) {
|
|
7338
|
-
sourcePaths.push(
|
|
7339
|
-
...await fg8(normalizedPath, {
|
|
7340
|
-
cwd: rootDir,
|
|
7341
|
-
dot: true,
|
|
7342
|
-
onlyFiles: true,
|
|
7343
|
-
followSymbolicLinks: false
|
|
7344
|
-
})
|
|
7345
|
-
);
|
|
7346
|
-
} else {
|
|
7347
|
-
sourcePaths.push(normalizedPath);
|
|
7348
|
-
}
|
|
7349
|
-
}
|
|
7350
|
-
}
|
|
7351
|
-
return uniqueSorted2(sourcePaths);
|
|
7352
|
-
};
|
|
7353
|
-
var writePathsFor = (workflow, routeIndexPath, truthDocs, routes) => {
|
|
7354
|
-
if (workflow === "truth-sync") {
|
|
7355
|
-
return uniqueSorted2([routeIndexPath, ...truthDocs]);
|
|
7356
|
-
}
|
|
7357
|
-
if (workflow === "truth-document") {
|
|
7358
|
-
return uniqueSorted2([routeIndexPath, ...truthDocs]);
|
|
7359
|
-
}
|
|
7360
|
-
return uniqueSorted2(routes.flatMap((route) => route.codeSurface));
|
|
7361
|
-
};
|
|
7362
|
-
var testCommandsFor = (affectedTests) => {
|
|
7363
|
-
return affectedTests.length === 0 ? ["npm test"] : [`npm test -- ${affectedTests.join(" ")}`];
|
|
7364
|
-
};
|
|
7365
|
-
var buildContextPack = async (cwd, options) => {
|
|
7366
|
-
const repoIndex = await buildRepoIndex(cwd);
|
|
7367
|
-
const rootDir = repoIndex.repository.root;
|
|
7368
|
-
const loadResult = await loadConfig(rootDir);
|
|
7369
|
-
const config = loadResult.config ?? (loadResult.status === "missing" ? createDefaultConfig() : null);
|
|
7370
|
-
const impactSet = options.base ? await buildImpactSet(rootDir, { base: options.base }) : null;
|
|
7371
|
-
const routeMap = impactSet ? repoIndex.routeMap : repoIndex.routeMap;
|
|
7372
|
-
const warnings = loadResult.status === "invalid" ? [...loadResult.diagnostics] : [];
|
|
7373
|
-
const truthDocPaths = impactSet?.affectedTruthDocs ?? (options.workflow === "truth-realize" ? [] : routeMap.routes.flatMap((route) => route.truthDocs));
|
|
7374
|
-
const contextRoutes = impactSet?.affectedRoutes ?? (options.workflow === "truth-realize" ? [] : routeMap.routes);
|
|
7375
|
-
if (options.workflow === "truth-realize" && !impactSet) {
|
|
7376
|
-
warnings.push({
|
|
7377
|
-
category: "context-pack",
|
|
7378
|
-
severity: "review",
|
|
7379
|
-
message: "truth-realize requires --base to derive bounded allowed write paths."
|
|
7380
|
-
});
|
|
7381
|
-
}
|
|
7382
|
-
const sourceOfTruthPaths = await sourceOfTruthPathsFor(rootDir, repoIndex.docs, truthDocPaths);
|
|
7383
|
-
const sourceFilePaths = uniqueSorted2([
|
|
7384
|
-
...impactSet?.changedFiles.filter((file) => !file.deleted).map((file) => file.path) ?? [],
|
|
7385
|
-
...sourceOfTruthPaths
|
|
7386
|
-
]);
|
|
7387
|
-
return {
|
|
7388
|
-
schemaVersion: "context-pack/v0",
|
|
7389
|
-
workflow: options.workflow,
|
|
7390
|
-
base: options.base ?? null,
|
|
7391
|
-
impactSet,
|
|
7392
|
-
routeMap,
|
|
7393
|
-
allowedWritePaths: config === null ? [] : writePathsFor(
|
|
7394
|
-
options.workflow,
|
|
7395
|
-
config.truthmark.paths.routesIndex,
|
|
7396
|
-
truthDocPaths,
|
|
7397
|
-
contextRoutes
|
|
7398
|
-
),
|
|
7399
|
-
truthDocs: await documentsFor(rootDir, truthDocPaths, warnings),
|
|
7400
|
-
sourceFiles: await sourceFilesFor(rootDir, sourceFilePaths, warnings),
|
|
7401
|
-
testCommands: testCommandsFor(impactSet?.affectedTests ?? []),
|
|
7402
|
-
warnings
|
|
7403
|
-
};
|
|
7404
|
-
};
|
|
7405
|
-
|
|
7406
|
-
// src/context-pack/render.ts
|
|
7407
|
-
var renderContextPackMarkdown = (pack) => {
|
|
7408
|
-
const lines = [
|
|
7409
|
-
`# Truthmark ContextPack (${pack.workflow})`,
|
|
7410
|
-
"",
|
|
7411
|
-
`Schema: ${pack.schemaVersion}`,
|
|
7412
|
-
`Base: ${pack.base ?? "none"}`,
|
|
7413
|
-
"",
|
|
7414
|
-
"## Allowed Write Paths",
|
|
7415
|
-
...pack.allowedWritePaths.map((filePath) => `- ${filePath}`),
|
|
7416
|
-
"",
|
|
7417
|
-
"## Truth Docs",
|
|
7418
|
-
...pack.truthDocs.map((doc) => `- ${doc.path}${doc.truncated ? " (truncated)" : ""}`),
|
|
7419
|
-
"",
|
|
7420
|
-
"## Source Files",
|
|
7421
|
-
...pack.sourceFiles.map((file) => `- ${file.path}${file.truncated ? " (truncated)" : ""}`),
|
|
7422
|
-
"",
|
|
7423
|
-
"## Test Commands",
|
|
7424
|
-
...pack.testCommands.map((command) => `- ${command}`)
|
|
7425
|
-
];
|
|
7426
|
-
return `${lines.join("\n")}
|
|
7427
|
-
`;
|
|
7428
|
-
};
|
|
7429
|
-
|
|
7430
7713
|
// src/workflow-state/build.ts
|
|
7431
7714
|
import { execa as execa5 } from "execa";
|
|
7432
7715
|
|
|
@@ -7533,7 +7816,7 @@ var applicabilityFor = (workflow, diagnostics, impactSet) => {
|
|
|
7533
7816
|
return { state: isWriteCapable(workflow) ? "blocked" : "not_applicable", reasons };
|
|
7534
7817
|
}
|
|
7535
7818
|
if (workflow === "truthmark-sync" && !impactSet) {
|
|
7536
|
-
reasons.push("truthmark-sync requires --base to derive
|
|
7819
|
+
reasons.push("truthmark-sync requires --base to derive changed-file impact before exposing sync write paths.");
|
|
7537
7820
|
return { state: "blocked", reasons };
|
|
7538
7821
|
}
|
|
7539
7822
|
if (workflow === "truthmark-realize" && !impactSet) {
|
|
@@ -7555,9 +7838,8 @@ var contextDataFor = (workflow, repoIndex, config, impactSet) => {
|
|
|
7555
7838
|
return {};
|
|
7556
7839
|
}
|
|
7557
7840
|
const routeFiles = routeFilesFor(repoIndex);
|
|
7558
|
-
const
|
|
7559
|
-
|
|
7560
|
-
);
|
|
7841
|
+
const indexedTruthDocs = uniqueSorted4(repoIndex.routeMap.routes.flatMap((route) => route.truthDocs));
|
|
7842
|
+
const truthDocs = workflow === "truthmark-sync" ? indexedTruthDocs : uniqueSorted4(impactSet?.affectedTruthDocs ?? indexedTruthDocs);
|
|
7561
7843
|
return {
|
|
7562
7844
|
routeIndexPath: config.truthmark.paths.routesIndex,
|
|
7563
7845
|
routeFiles,
|
|
@@ -7576,7 +7858,7 @@ var nextStepsFor = (workflow, applicability, comparisonBase) => {
|
|
|
7576
7858
|
}
|
|
7577
7859
|
if ((workflow === "truthmark-sync" || workflow === "truthmark-realize") && applicability.state === "blocked" && !comparisonBase) {
|
|
7578
7860
|
return [
|
|
7579
|
-
workflow === "truthmark-sync" ? "Rerun with --base <ref> so Truthmark can derive
|
|
7861
|
+
workflow === "truthmark-sync" ? "Rerun with --base <ref> so Truthmark can derive changed-file impact before exposing sync write paths." : "Rerun with --base <ref> so Truthmark can derive bounded allowed code-write paths."
|
|
7580
7862
|
];
|
|
7581
7863
|
}
|
|
7582
7864
|
return [];
|
|
@@ -7612,7 +7894,8 @@ var buildWorkflowState = async (cwd, options) => {
|
|
|
7612
7894
|
checks: {
|
|
7613
7895
|
required: [...manifestEntry.requiredGates],
|
|
7614
7896
|
recommended: [...manifestEntry.positiveTriggers],
|
|
7615
|
-
helpers: helperCommandsFor2(options.workflow)
|
|
7897
|
+
helpers: helperCommandsFor2(options.workflow),
|
|
7898
|
+
affectedTests: impactSet?.affectedTests ?? []
|
|
7616
7899
|
},
|
|
7617
7900
|
nextSteps: nextStepsFor(options.workflow, applicability, comparisonBase),
|
|
7618
7901
|
reportSections: [...manifestEntry.reportSections]
|
|
@@ -7620,7 +7903,7 @@ var buildWorkflowState = async (cwd, options) => {
|
|
|
7620
7903
|
};
|
|
7621
7904
|
|
|
7622
7905
|
// src/cli/handlers.ts
|
|
7623
|
-
import
|
|
7906
|
+
import fs21 from "fs/promises";
|
|
7624
7907
|
|
|
7625
7908
|
// src/agents/workflow-helper-validation.ts
|
|
7626
7909
|
import { parse as parseYaml2 } from "yaml";
|
|
@@ -7982,12 +8265,6 @@ var runImpact = async (options) => {
|
|
|
7982
8265
|
}
|
|
7983
8266
|
};
|
|
7984
8267
|
};
|
|
7985
|
-
var isContextPackWorkflow = (value) => {
|
|
7986
|
-
return value === "truth-sync" || value === "truth-document" || value === "truth-realize";
|
|
7987
|
-
};
|
|
7988
|
-
var isContextMarkdownFormat = (value) => {
|
|
7989
|
-
return value === void 0 || value === "markdown";
|
|
7990
|
-
};
|
|
7991
8268
|
var isTruthmarkWorkflowId = (value) => {
|
|
7992
8269
|
return typeof value === "string" && TRUTHMARK_WORKFLOW_IDS.includes(value);
|
|
7993
8270
|
};
|
|
@@ -8007,7 +8284,7 @@ var invalidWorkflowResult = (command, workflow) => ({
|
|
|
8007
8284
|
});
|
|
8008
8285
|
var readHelperFile = async (filePath, helper) => {
|
|
8009
8286
|
try {
|
|
8010
|
-
return await
|
|
8287
|
+
return await fs21.readFile(filePath, "utf8");
|
|
8011
8288
|
} catch (error) {
|
|
8012
8289
|
const message = error instanceof Error ? error.message : String(error);
|
|
8013
8290
|
return { ok: false, helper, errors: [`could not read file: ${message}`] };
|
|
@@ -8032,63 +8309,6 @@ var runValidateWriteLease = async (leaseFile, changedFilesFile) => {
|
|
|
8032
8309
|
}
|
|
8033
8310
|
return validateWriteLeaseText(leaseText, changedText);
|
|
8034
8311
|
};
|
|
8035
|
-
var runContext = async (options) => {
|
|
8036
|
-
if (!isContextPackWorkflow(options.workflow)) {
|
|
8037
|
-
return {
|
|
8038
|
-
command: "context",
|
|
8039
|
-
summary: "Truthmark context requires a supported --workflow value.",
|
|
8040
|
-
diagnostics: [
|
|
8041
|
-
{
|
|
8042
|
-
category: "context-pack",
|
|
8043
|
-
severity: "error",
|
|
8044
|
-
message: "truthmark context requires --workflow truth-sync, truth-document, or truth-realize."
|
|
8045
|
-
}
|
|
8046
|
-
]
|
|
8047
|
-
};
|
|
8048
|
-
}
|
|
8049
|
-
if (options.format === "json") {
|
|
8050
|
-
return {
|
|
8051
|
-
command: "context",
|
|
8052
|
-
summary: "Truthmark context no longer supports JSON ContextPack output.",
|
|
8053
|
-
diagnostics: [
|
|
8054
|
-
{
|
|
8055
|
-
category: "context-pack",
|
|
8056
|
-
severity: "error",
|
|
8057
|
-
message: "JSON ContextPack output was removed in v2; use --format markdown."
|
|
8058
|
-
}
|
|
8059
|
-
]
|
|
8060
|
-
};
|
|
8061
|
-
}
|
|
8062
|
-
if (!isContextMarkdownFormat(options.format)) {
|
|
8063
|
-
return {
|
|
8064
|
-
command: "context",
|
|
8065
|
-
summary: "Truthmark context requires a supported --format value.",
|
|
8066
|
-
diagnostics: [
|
|
8067
|
-
{
|
|
8068
|
-
category: "context-pack",
|
|
8069
|
-
severity: "error",
|
|
8070
|
-
message: "truthmark context supports only --format markdown; JSON ContextPack output was removed in v2."
|
|
8071
|
-
}
|
|
8072
|
-
]
|
|
8073
|
-
};
|
|
8074
|
-
}
|
|
8075
|
-
const contextPack = await buildContextPack(process.cwd(), {
|
|
8076
|
-
workflow: options.workflow,
|
|
8077
|
-
base: options.base
|
|
8078
|
-
});
|
|
8079
|
-
const diagnostics = contextPack.warnings;
|
|
8080
|
-
const summary = `Truthmark context generated ${contextPack.workflow} ContextPack with ${diagnostics.length} warnings.`;
|
|
8081
|
-
const markdown = renderContextPackMarkdown(contextPack);
|
|
8082
|
-
return {
|
|
8083
|
-
command: "context",
|
|
8084
|
-
summary,
|
|
8085
|
-
diagnostics,
|
|
8086
|
-
data: {
|
|
8087
|
-
markdown,
|
|
8088
|
-
summary
|
|
8089
|
-
}
|
|
8090
|
-
};
|
|
8091
|
-
};
|
|
8092
8312
|
var runWorkflowStatus = async (options) => {
|
|
8093
8313
|
if (!isTruthmarkWorkflowId(options.workflow)) {
|
|
8094
8314
|
return invalidWorkflowResult("workflow status", options.workflow);
|
|
@@ -8123,14 +8343,6 @@ var writeResult = (result, options) => {
|
|
|
8123
8343
|
`);
|
|
8124
8344
|
markFailedWhenErrorDiagnosticsExist(result);
|
|
8125
8345
|
};
|
|
8126
|
-
var writeContextResult = (result, options) => {
|
|
8127
|
-
if (!options.json && typeof result.data?.markdown === "string") {
|
|
8128
|
-
process.stdout.write(result.data.markdown);
|
|
8129
|
-
markFailedWhenErrorDiagnosticsExist(result);
|
|
8130
|
-
return;
|
|
8131
|
-
}
|
|
8132
|
-
writeResult(result, options);
|
|
8133
|
-
};
|
|
8134
8346
|
var renderValidationHuman = (result) => {
|
|
8135
8347
|
if (result.ok === true) {
|
|
8136
8348
|
return [`${result.helper}: ok`, ...result.checks.map((check) => `- ${check}`)].join("\n");
|
|
@@ -8175,7 +8387,7 @@ var buildProgram = () => {
|
|
|
8175
8387
|
writeResult(await runCheck2({ base: options.base }), options);
|
|
8176
8388
|
});
|
|
8177
8389
|
addJsonOption(
|
|
8178
|
-
program.command("index").description("
|
|
8390
|
+
program.command("index").description("Inspect derived Truthmark workflow routing metadata for the current checkout.")
|
|
8179
8391
|
).action(async (options) => {
|
|
8180
8392
|
writeResult(await runIndex(), options);
|
|
8181
8393
|
});
|
|
@@ -8184,18 +8396,6 @@ var buildProgram = () => {
|
|
|
8184
8396
|
).action(async (options) => {
|
|
8185
8397
|
writeResult(await runImpact({ base: options.base }), options);
|
|
8186
8398
|
});
|
|
8187
|
-
addJsonOption(
|
|
8188
|
-
program.command("context").description("Generate a bounded workflow context pack.").requiredOption("--workflow <workflow>", "Workflow name: truth-sync, truth-document, or truth-realize").option("--base <ref>", "Base Git ref for impact-backed packs").option("--format <format>", "Output format: markdown", "markdown")
|
|
8189
|
-
).action(async (options) => {
|
|
8190
|
-
writeContextResult(
|
|
8191
|
-
await runContext({
|
|
8192
|
-
workflow: options.workflow,
|
|
8193
|
-
base: options.base,
|
|
8194
|
-
format: options.format
|
|
8195
|
-
}),
|
|
8196
|
-
options
|
|
8197
|
-
);
|
|
8198
|
-
});
|
|
8199
8399
|
const workflow = program.command("workflow").description("Inspect agent-facing Truthmark workflow state.");
|
|
8200
8400
|
addJsonOption(
|
|
8201
8401
|
workflow.command("status").description("Return schema-versioned workflow state for a canonical workflow ID.").option("--workflow <workflow>", "Canonical workflow ID, such as truthmark-sync").option("--base <ref>", "Base Git ref for impact-backed workflow state")
|