truthmark 2.2.7 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -22
- package/dist/main.js +1985 -1897
- package/dist/main.js.map +1 -1
- package/docs/README.md +7 -7
- package/docs/readmes/README.ar.md +12 -21
- package/docs/readmes/README.de.md +12 -21
- package/docs/readmes/README.el.md +12 -21
- package/docs/readmes/README.es.md +12 -21
- package/docs/readmes/README.fr.md +12 -21
- package/docs/readmes/README.id.md +12 -21
- package/docs/readmes/README.it.md +12 -21
- package/docs/readmes/README.ja.md +12 -21
- package/docs/readmes/README.ko.md +12 -21
- package/docs/readmes/README.pl.md +12 -21
- package/docs/readmes/README.pt.md +12 -21
- package/docs/readmes/README.ru.md +12 -21
- package/docs/readmes/README.tr.md +12 -21
- package/docs/readmes/README.vi.md +12 -21
- package/docs/readmes/README.zh.md +12 -21
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -42,8 +42,14 @@ var renderJson = (result) => {
|
|
|
42
42
|
return JSON.stringify(toStableValue(result), null, 2);
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
// src/
|
|
46
|
-
import
|
|
45
|
+
// src/init/init.ts
|
|
46
|
+
import fs6 from "fs/promises";
|
|
47
|
+
|
|
48
|
+
// src/config/load.ts
|
|
49
|
+
import fs2 from "fs/promises";
|
|
50
|
+
import path2 from "path";
|
|
51
|
+
import { Ajv } from "ajv";
|
|
52
|
+
import { parse } from "yaml";
|
|
47
53
|
|
|
48
54
|
// src/fs/paths.ts
|
|
49
55
|
import path from "path";
|
|
@@ -213,81 +219,6 @@ var ensureRepoFile = async (rootDir, relativePath, content) => {
|
|
|
213
219
|
};
|
|
214
220
|
};
|
|
215
221
|
|
|
216
|
-
// src/git/repository.ts
|
|
217
|
-
import fs2 from "fs/promises";
|
|
218
|
-
import { realpathSync } from "fs";
|
|
219
|
-
import path2 from "path";
|
|
220
|
-
import { execa } from "execa";
|
|
221
|
-
var realpathOrResolved = async (targetPath) => {
|
|
222
|
-
try {
|
|
223
|
-
return await fs2.realpath(targetPath);
|
|
224
|
-
} catch {
|
|
225
|
-
return path2.resolve(targetPath);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
var runGit = async (cwd, args, reject = true) => {
|
|
229
|
-
const result = await execa("git", args, { cwd, reject });
|
|
230
|
-
return {
|
|
231
|
-
stdout: result.stdout,
|
|
232
|
-
exitCode: result.exitCode ?? 1
|
|
233
|
-
};
|
|
234
|
-
};
|
|
235
|
-
var getGitRepository = async (cwd) => {
|
|
236
|
-
const worktreePath = await realpathOrResolved(
|
|
237
|
-
(await runGit(cwd, ["rev-parse", "--show-toplevel"])).stdout.trim()
|
|
238
|
-
);
|
|
239
|
-
const commonDirOutput = (await runGit(cwd, ["rev-parse", "--git-common-dir"])).stdout.trim();
|
|
240
|
-
const commonDir = await realpathOrResolved(path2.resolve(worktreePath, commonDirOutput));
|
|
241
|
-
const repositoryRoot = path2.basename(commonDir) === ".git" ? path2.dirname(commonDir) : worktreePath;
|
|
242
|
-
const branchResult = await runGit(cwd, ["symbolic-ref", "--quiet", "--short", "HEAD"], false);
|
|
243
|
-
const headResult = await runGit(cwd, ["rev-parse", "--verify", "HEAD"], false);
|
|
244
|
-
const branchName = branchResult.exitCode === 0 ? branchResult.stdout.trim() : null;
|
|
245
|
-
const headSha = headResult.exitCode === 0 ? headResult.stdout.trim() : null;
|
|
246
|
-
const isDetached = branchName === null;
|
|
247
|
-
const isUnborn = !isDetached && headSha === null;
|
|
248
|
-
return {
|
|
249
|
-
repositoryRoot,
|
|
250
|
-
worktreePath,
|
|
251
|
-
branchName,
|
|
252
|
-
headSha,
|
|
253
|
-
isDetached,
|
|
254
|
-
isUnborn
|
|
255
|
-
};
|
|
256
|
-
};
|
|
257
|
-
var resolveWorktreePath = (repository, relativePath) => {
|
|
258
|
-
const resolvedPath = path2.resolve(repository.worktreePath, relativePath);
|
|
259
|
-
let currentPath = resolvedPath;
|
|
260
|
-
const missingSegments = [];
|
|
261
|
-
const resolveContainedPath = () => {
|
|
262
|
-
while (true) {
|
|
263
|
-
try {
|
|
264
|
-
return missingSegments.reduceRight((resolvedExistingPath, segment) => {
|
|
265
|
-
return path2.join(resolvedExistingPath, segment);
|
|
266
|
-
}, realpathSync(currentPath));
|
|
267
|
-
} catch (error) {
|
|
268
|
-
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
269
|
-
throw error;
|
|
270
|
-
}
|
|
271
|
-
const parentPath = path2.dirname(currentPath);
|
|
272
|
-
if (parentPath === currentPath) {
|
|
273
|
-
return resolvedPath;
|
|
274
|
-
}
|
|
275
|
-
missingSegments.unshift(path2.basename(currentPath));
|
|
276
|
-
currentPath = parentPath;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
const containedPath = resolveContainedPath();
|
|
281
|
-
if (containedPath !== repository.worktreePath && !containedPath.startsWith(`${repository.worktreePath}${path2.sep}`)) {
|
|
282
|
-
throw new Error("resolved path must stay inside the active worktree");
|
|
283
|
-
}
|
|
284
|
-
return resolvedPath;
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
// src/templates/init-files.ts
|
|
288
|
-
import path3 from "path";
|
|
289
|
-
import { stringify } from "yaml";
|
|
290
|
-
|
|
291
222
|
// src/config/schema.ts
|
|
292
223
|
var SUPPORTED_PLATFORMS = [
|
|
293
224
|
"codex",
|
|
@@ -457,348 +388,276 @@ var createDefaultConfig = () => ({
|
|
|
457
388
|
ignore: ["node_modules/**", "vendor/**", "dist/**", "build/**"]
|
|
458
389
|
});
|
|
459
390
|
|
|
460
|
-
// src/
|
|
461
|
-
|
|
462
|
-
var
|
|
463
|
-
|
|
464
|
-
"
|
|
465
|
-
"
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
"engineering-operations",
|
|
469
|
-
"engineering-test-behavior"
|
|
470
|
-
];
|
|
471
|
-
var uniqueSorted = (values) => [...new Set(values)].sort();
|
|
472
|
-
var mergeTruthDocumentEntryRelationships = (first, second) => ({
|
|
473
|
-
...first,
|
|
474
|
-
realizedBy: uniqueSorted([...first.realizedBy, ...second.realizedBy]),
|
|
475
|
-
realizes: uniqueSorted([...first.realizes, ...second.realizes]),
|
|
476
|
-
dependsOn: uniqueSorted([...first.dependsOn, ...second.dependsOn])
|
|
391
|
+
// src/config/load.ts
|
|
392
|
+
var ajv = new Ajv({ allErrors: true });
|
|
393
|
+
var validateTruthmarkConfig = ajv.compile(truthmarkConfigSchema);
|
|
394
|
+
var toConfigDiagnostic = (message, file) => ({
|
|
395
|
+
category: "config",
|
|
396
|
+
severity: "error",
|
|
397
|
+
message,
|
|
398
|
+
file
|
|
477
399
|
});
|
|
478
|
-
var
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
482
|
-
};
|
|
483
|
-
var createAreaDiagnostic = (message, area, severity = "error") => {
|
|
484
|
-
return {
|
|
485
|
-
category: "area-index",
|
|
486
|
-
severity,
|
|
487
|
-
message,
|
|
488
|
-
area
|
|
489
|
-
};
|
|
400
|
+
var normalizeRepoRelativePath = (value) => {
|
|
401
|
+
const slashNormalized = value.replace(/\\/gu, "/");
|
|
402
|
+
return path2.posix.normalize(slashNormalized).replace(/\/+$/u, "");
|
|
490
403
|
};
|
|
491
|
-
var
|
|
492
|
-
|
|
404
|
+
var isUnsafeRepoRelativePath = (value) => {
|
|
405
|
+
const slashNormalized = value.replace(/\\/gu, "/");
|
|
406
|
+
const normalized = normalizeRepoRelativePath(value);
|
|
407
|
+
const parts = slashNormalized.split("/");
|
|
408
|
+
return normalized.length === 0 || normalized === "." || normalized === ".." || path2.isAbsolute(value) || path2.posix.isAbsolute(slashNormalized) || path2.win32.isAbsolute(value) || /^[A-Za-z]:/u.test(value) || normalized.startsWith("../") || parts.includes("..");
|
|
493
409
|
};
|
|
494
|
-
var
|
|
495
|
-
return
|
|
410
|
+
var joinWorkspacePath = (workspace, childPath) => {
|
|
411
|
+
return normalizeRepoRelativePath(`${workspace}/${childPath}`);
|
|
496
412
|
};
|
|
497
|
-
var
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
return "product-capability";
|
|
503
|
-
}
|
|
504
|
-
if (engineeringTruthRoot && normalizedPath.startsWith(`${engineeringTruthRoot}/`)) {
|
|
505
|
-
if (normalizedPath.includes("/contracts/")) return "engineering-contract";
|
|
506
|
-
if (normalizedPath.includes("/workflows/")) return "engineering-workflow";
|
|
507
|
-
if (normalizedPath.includes("/architecture/"))
|
|
508
|
-
return "engineering-architecture";
|
|
509
|
-
if (normalizedPath.includes("/operations/"))
|
|
510
|
-
return "engineering-operations";
|
|
511
|
-
if (normalizedPath.includes("/tests/")) return "engineering-test-behavior";
|
|
512
|
-
return "engineering-behavior";
|
|
513
|
-
}
|
|
514
|
-
return null;
|
|
413
|
+
var portalOutputFor = (workspace) => joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.portalOutput);
|
|
414
|
+
var pathsOverlap = (left, right) => {
|
|
415
|
+
const normalizedLeft = normalizeRepoRelativePath(left);
|
|
416
|
+
const normalizedRight = normalizeRepoRelativePath(right);
|
|
417
|
+
return normalizedLeft === normalizedRight || normalizedLeft.startsWith(`${normalizedRight}/`) || normalizedRight.startsWith(`${normalizedLeft}/`);
|
|
515
418
|
};
|
|
516
|
-
var
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
419
|
+
var CONFIG_PATH = ".truthmark/config.yml";
|
|
420
|
+
var FORBIDDEN_WORKSPACE_OVERLAPS = [
|
|
421
|
+
".git",
|
|
422
|
+
".truthmark",
|
|
423
|
+
"package.json",
|
|
424
|
+
"package-lock.json",
|
|
425
|
+
"pnpm-lock.yaml",
|
|
426
|
+
"yarn.lock",
|
|
427
|
+
"src",
|
|
428
|
+
"tests"
|
|
429
|
+
];
|
|
430
|
+
var unsupportedShapeDiagnostics = (parsedConfig, configPath) => {
|
|
431
|
+
if (!parsedConfig || typeof parsedConfig !== "object" || Array.isArray(parsedConfig)) {
|
|
432
|
+
return [];
|
|
522
433
|
}
|
|
523
|
-
|
|
524
|
-
|
|
434
|
+
const record = parsedConfig;
|
|
435
|
+
const diagnostics = [];
|
|
436
|
+
if (record.version !== 2) {
|
|
437
|
+
diagnostics.push(
|
|
438
|
+
toConfigDiagnostic(
|
|
439
|
+
"Unsupported Truthmark config shape. This release requires version: 2 with a truthmark workspace block.",
|
|
440
|
+
configPath
|
|
441
|
+
)
|
|
442
|
+
);
|
|
525
443
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
return "product";
|
|
444
|
+
if ("docs" in record || "authority" in record) {
|
|
445
|
+
diagnostics.push(
|
|
446
|
+
toConfigDiagnostic(
|
|
447
|
+
"Unsupported Truthmark config shape. Remove old docs.roots and legacy authority settings; use version: 2 truthmark.workspace paths.",
|
|
448
|
+
configPath
|
|
449
|
+
)
|
|
450
|
+
);
|
|
534
451
|
}
|
|
535
|
-
return
|
|
452
|
+
return diagnostics;
|
|
536
453
|
};
|
|
537
|
-
var
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
454
|
+
var validateWorkspacePaths = (rawConfig, configPath) => {
|
|
455
|
+
const diagnostics = [];
|
|
456
|
+
const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
|
|
457
|
+
if (isUnsafeRepoRelativePath(rawConfig.truthmark.workspace) || FORBIDDEN_WORKSPACE_OVERLAPS.some(
|
|
458
|
+
(forbidden) => pathsOverlap(workspace, forbidden)
|
|
459
|
+
)) {
|
|
460
|
+
diagnostics.push(
|
|
461
|
+
toConfigDiagnostic(
|
|
462
|
+
"truthmark.workspace must be a non-empty repo-relative directory that does not overlap repository control, package, source, test, or instruction paths.",
|
|
463
|
+
configPath
|
|
464
|
+
)
|
|
465
|
+
);
|
|
544
466
|
}
|
|
545
|
-
return
|
|
467
|
+
return diagnostics;
|
|
546
468
|
};
|
|
547
|
-
var
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
|
|
469
|
+
var normalizeConfig = (rawConfig) => {
|
|
470
|
+
const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
|
|
471
|
+
const routesIndex = joinWorkspacePath(
|
|
472
|
+
workspace,
|
|
473
|
+
DERIVED_TRUTHMARK_PATHS.routesIndex
|
|
551
474
|
);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
475
|
+
const routeAreasRoot = joinWorkspacePath(
|
|
476
|
+
workspace,
|
|
477
|
+
DERIVED_TRUTHMARK_PATHS.routeAreasRoot
|
|
478
|
+
);
|
|
479
|
+
const productTruthRoot = joinWorkspacePath(
|
|
480
|
+
workspace,
|
|
481
|
+
DERIVED_TRUTHMARK_PATHS.productTruthRoot
|
|
482
|
+
);
|
|
483
|
+
const engineeringTruthRoot = joinWorkspacePath(
|
|
484
|
+
workspace,
|
|
485
|
+
DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
|
|
486
|
+
);
|
|
487
|
+
const templatesRoot = joinWorkspacePath(
|
|
488
|
+
workspace,
|
|
489
|
+
DERIVED_TRUTHMARK_PATHS.templatesRoot
|
|
490
|
+
);
|
|
491
|
+
const portalOutput = portalOutputFor(workspace);
|
|
492
|
+
const portalTemplate = joinWorkspacePath(
|
|
493
|
+
workspace,
|
|
494
|
+
DERIVED_TRUTHMARK_PATHS.portalTemplate
|
|
557
495
|
);
|
|
558
496
|
return {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
497
|
+
version: rawConfig.version,
|
|
498
|
+
platforms: rawConfig.platforms ?? [...DEFAULT_PLATFORMS],
|
|
499
|
+
truthmark: {
|
|
500
|
+
workspace,
|
|
501
|
+
routes: {
|
|
502
|
+
index: DERIVED_TRUTHMARK_PATHS.routesIndex,
|
|
503
|
+
areas: DERIVED_TRUTHMARK_PATHS.routeAreasRoot,
|
|
504
|
+
defaultArea: DERIVED_TRUTHMARK_PATHS.defaultArea,
|
|
505
|
+
maxDelegationDepth: DERIVED_TRUTHMARK_PATHS.maxDelegationDepth
|
|
506
|
+
},
|
|
507
|
+
truth: {
|
|
508
|
+
productRoot: DERIVED_TRUTHMARK_PATHS.productTruthRoot,
|
|
509
|
+
engineeringRoot: DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
|
|
510
|
+
},
|
|
511
|
+
templates: {
|
|
512
|
+
root: DERIVED_TRUTHMARK_PATHS.templatesRoot
|
|
513
|
+
},
|
|
514
|
+
generated: {
|
|
515
|
+
portal: {
|
|
516
|
+
enabled: rawConfig.truthmark.generated.portal.enabled
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
paths: {
|
|
520
|
+
routesIndex,
|
|
521
|
+
routeAreasRoot,
|
|
522
|
+
productTruthRoot,
|
|
523
|
+
engineeringTruthRoot,
|
|
524
|
+
templatesRoot,
|
|
525
|
+
portalOutput,
|
|
526
|
+
portalTemplate
|
|
527
|
+
},
|
|
528
|
+
controlledPaths: [
|
|
529
|
+
routesIndex,
|
|
530
|
+
`${routeAreasRoot}/**/*.md`,
|
|
531
|
+
`${productTruthRoot}/**/*.md`,
|
|
532
|
+
`${engineeringTruthRoot}/**/*.md`,
|
|
533
|
+
`${templatesRoot}/*.md`
|
|
534
|
+
]
|
|
535
|
+
},
|
|
536
|
+
frontmatter: {
|
|
537
|
+
required: rawConfig.frontmatter?.required ?? [],
|
|
538
|
+
recommended: rawConfig.frontmatter?.recommended ?? []
|
|
539
|
+
},
|
|
540
|
+
ignore: rawConfig.ignore ?? []
|
|
593
541
|
};
|
|
594
542
|
};
|
|
595
|
-
var
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
return {
|
|
599
|
-
truthDocuments: [],
|
|
600
|
-
truthDocumentEntries: [],
|
|
601
|
-
diagnostics: []
|
|
602
|
-
};
|
|
543
|
+
var compatibilityDiagnostics = (rawConfig, configPath) => {
|
|
544
|
+
if (!("instruction_targets" in rawConfig)) {
|
|
545
|
+
return [];
|
|
603
546
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
547
|
+
return [
|
|
548
|
+
{
|
|
549
|
+
category: "config",
|
|
550
|
+
severity: "review",
|
|
551
|
+
message: "instruction_targets is accepted for compatibility but ignored; select platforms to control managed instruction-file writes.",
|
|
552
|
+
file: configPath
|
|
553
|
+
}
|
|
554
|
+
];
|
|
555
|
+
};
|
|
556
|
+
var loadConfig = async (rootDir) => {
|
|
557
|
+
const absolutePath = resolveRepoPath(rootDir, CONFIG_PATH);
|
|
558
|
+
let source;
|
|
559
|
+
try {
|
|
560
|
+
source = await fs2.readFile(absolutePath, "utf8");
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
563
|
+
return {
|
|
564
|
+
status: "missing",
|
|
565
|
+
config: null,
|
|
566
|
+
diagnostics: [
|
|
567
|
+
toConfigDiagnostic("Missing .truthmark/config.yml.", CONFIG_PATH)
|
|
568
|
+
],
|
|
569
|
+
configPath: CONFIG_PATH
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
throw error;
|
|
615
573
|
}
|
|
616
|
-
let
|
|
574
|
+
let parsedConfig;
|
|
617
575
|
try {
|
|
618
|
-
|
|
619
|
-
sectionLines.slice(
|
|
620
|
-
yamlFenceRange.openingFenceIndex + 1,
|
|
621
|
-
yamlFenceRange.closingFenceIndex
|
|
622
|
-
).join("\n")
|
|
623
|
-
);
|
|
576
|
+
parsedConfig = parse(source);
|
|
624
577
|
} catch (error) {
|
|
625
578
|
return {
|
|
626
|
-
|
|
627
|
-
|
|
579
|
+
status: "invalid",
|
|
580
|
+
config: null,
|
|
628
581
|
diagnostics: [
|
|
629
|
-
|
|
630
|
-
`
|
|
631
|
-
|
|
582
|
+
toConfigDiagnostic(
|
|
583
|
+
`Invalid YAML: ${error instanceof Error ? error.message : String(error)}`,
|
|
584
|
+
CONFIG_PATH
|
|
632
585
|
)
|
|
633
|
-
]
|
|
586
|
+
],
|
|
587
|
+
configPath: CONFIG_PATH
|
|
634
588
|
};
|
|
635
589
|
}
|
|
636
|
-
const
|
|
637
|
-
|
|
590
|
+
const unsupportedDiagnostics = unsupportedShapeDiagnostics(
|
|
591
|
+
parsedConfig,
|
|
592
|
+
CONFIG_PATH
|
|
593
|
+
);
|
|
594
|
+
if (unsupportedDiagnostics.length > 0) {
|
|
638
595
|
return {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
diagnostics:
|
|
642
|
-
|
|
643
|
-
`Area ${areaName} must define a truth_documents array inside the fenced YAML block.`,
|
|
644
|
-
areaName
|
|
645
|
-
)
|
|
646
|
-
]
|
|
596
|
+
status: "invalid",
|
|
597
|
+
config: null,
|
|
598
|
+
diagnostics: unsupportedDiagnostics,
|
|
599
|
+
configPath: CONFIG_PATH
|
|
647
600
|
};
|
|
648
601
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
realizes: parseStringListField(rawEntry, "realizes"),
|
|
676
|
-
dependsOn: parseStringListField(rawEntry, "depends_on")
|
|
677
|
-
});
|
|
602
|
+
if (!validateTruthmarkConfig(parsedConfig)) {
|
|
603
|
+
return {
|
|
604
|
+
status: "invalid",
|
|
605
|
+
config: null,
|
|
606
|
+
diagnostics: (validateTruthmarkConfig.errors ?? []).map(
|
|
607
|
+
(error) => {
|
|
608
|
+
const propertyPath = error.instancePath || "/";
|
|
609
|
+
const additionalProperty = error.keyword === "additionalProperties" && error.params && "additionalProperty" in error.params ? String(error.params.additionalProperty) : null;
|
|
610
|
+
const message = additionalProperty ? `${propertyPath} additional property ${additionalProperty} is not allowed` : `${propertyPath} ${error.message ?? "is invalid"}`.trim();
|
|
611
|
+
return toConfigDiagnostic(message, CONFIG_PATH);
|
|
612
|
+
}
|
|
613
|
+
),
|
|
614
|
+
configPath: CONFIG_PATH
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
const pathDiagnostics = validateWorkspacePaths(
|
|
618
|
+
parsedConfig,
|
|
619
|
+
CONFIG_PATH
|
|
620
|
+
);
|
|
621
|
+
if (pathDiagnostics.length > 0) {
|
|
622
|
+
return {
|
|
623
|
+
status: "invalid",
|
|
624
|
+
config: null,
|
|
625
|
+
diagnostics: pathDiagnostics,
|
|
626
|
+
configPath: CONFIG_PATH
|
|
627
|
+
};
|
|
678
628
|
}
|
|
679
629
|
return {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
diagnostics
|
|
630
|
+
status: "loaded",
|
|
631
|
+
config: normalizeConfig(parsedConfig),
|
|
632
|
+
diagnostics: compatibilityDiagnostics(
|
|
633
|
+
parsedConfig,
|
|
634
|
+
CONFIG_PATH
|
|
635
|
+
),
|
|
636
|
+
configPath: CONFIG_PATH
|
|
683
637
|
};
|
|
684
638
|
};
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
sectionLines,
|
|
692
|
-
areaName,
|
|
693
|
-
options
|
|
694
|
-
);
|
|
695
|
-
if (yamlResult.diagnostics.length > 0 || yamlFenceRange.closingFenceIndex === null) {
|
|
696
|
-
return yamlResult;
|
|
697
|
-
}
|
|
698
|
-
return yamlResult;
|
|
639
|
+
|
|
640
|
+
// src/config/render.ts
|
|
641
|
+
import { parse as parse2, parseDocument, stringify } from "yaml";
|
|
642
|
+
var normalizePlatforms = (platforms) => {
|
|
643
|
+
const selected = new Set(platforms);
|
|
644
|
+
return SUPPORTED_PLATFORMS.filter((platform) => selected.has(platform));
|
|
699
645
|
};
|
|
700
|
-
var
|
|
701
|
-
const
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
currentSections.get("Truth documents") ?? [],
|
|
716
|
-
currentAreaName,
|
|
717
|
-
options
|
|
718
|
-
);
|
|
719
|
-
const { truthDocuments, truthDocumentEntries } = truthDocumentResult;
|
|
720
|
-
const areaFiles = parseListSection(currentSections.get("Area files") ?? []);
|
|
721
|
-
const codeSurface = parseListSection(
|
|
722
|
-
currentSections.get("Code surface") ?? []
|
|
723
|
-
);
|
|
724
|
-
const updateTruthWhen = parseListSection(
|
|
725
|
-
currentSections.get("Update truth when") ?? []
|
|
726
|
-
);
|
|
727
|
-
const areaKey = slugify(currentAreaName);
|
|
728
|
-
const areaId = areaKey.length > 0 ? areaKey : `area-${areaIndex}`;
|
|
729
|
-
const hasTruthDocuments = truthDocuments.length > 0;
|
|
730
|
-
const hasAreaFiles = areaFiles.length > 0;
|
|
731
|
-
areaIndex += 1;
|
|
732
|
-
diagnostics.push(...truthDocumentResult.diagnostics);
|
|
733
|
-
if (hasTruthDocuments) {
|
|
734
|
-
truthDocumentReferences.push({
|
|
735
|
-
id: areaId,
|
|
736
|
-
name: currentAreaName,
|
|
737
|
-
key: areaKey,
|
|
738
|
-
truthDocuments,
|
|
739
|
-
truthDocumentEntries
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
if (hasTruthDocuments === hasAreaFiles || codeSurface.length === 0 || updateTruthWhen.length === 0) {
|
|
743
|
-
diagnostics.push(
|
|
744
|
-
createAreaDiagnostic(
|
|
745
|
-
`Area ${currentAreaName} must define exactly one of Truth documents or Area files, plus Code surface and Update truth when sections.`,
|
|
746
|
-
currentAreaName
|
|
747
|
-
)
|
|
748
|
-
);
|
|
749
|
-
} else if (hasAreaFiles) {
|
|
750
|
-
areaFileReferences.push({
|
|
751
|
-
id: areaId,
|
|
752
|
-
name: currentAreaName,
|
|
753
|
-
key: areaKey,
|
|
754
|
-
areaFiles,
|
|
755
|
-
codeSurface,
|
|
756
|
-
updateTruthWhen
|
|
757
|
-
});
|
|
758
|
-
} else {
|
|
759
|
-
areas.push({
|
|
760
|
-
id: areaId,
|
|
761
|
-
name: currentAreaName,
|
|
762
|
-
key: areaKey,
|
|
763
|
-
truthDocuments,
|
|
764
|
-
truthDocumentEntries,
|
|
765
|
-
codeSurface,
|
|
766
|
-
updateTruthWhen
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
currentAreaName = null;
|
|
770
|
-
currentSections = /* @__PURE__ */ new Map();
|
|
771
|
-
currentSectionName = null;
|
|
772
|
-
};
|
|
773
|
-
for (const line of lines) {
|
|
774
|
-
const areaHeadingMatch = line.match(/^\s{0,3}##\s+(.*)$/u);
|
|
775
|
-
if (areaHeadingMatch) {
|
|
776
|
-
const heading = areaHeadingMatch[1]?.trim() ?? null;
|
|
777
|
-
flushArea();
|
|
778
|
-
currentAreaName = heading === "Source References" ? null : heading;
|
|
779
|
-
continue;
|
|
780
|
-
}
|
|
781
|
-
if (!currentAreaName) {
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
if (/^(Truth documents|Area files|Code surface|Update truth when):$/u.test(
|
|
785
|
-
line.trim()
|
|
786
|
-
)) {
|
|
787
|
-
currentSectionName = line.trim().slice(0, -1);
|
|
788
|
-
currentSections.set(currentSectionName, []);
|
|
789
|
-
continue;
|
|
790
|
-
}
|
|
791
|
-
if (currentSectionName) {
|
|
792
|
-
currentSections.get(currentSectionName)?.push(line);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
flushArea();
|
|
796
|
-
return {
|
|
797
|
-
areas,
|
|
798
|
-
truthDocumentReferences,
|
|
799
|
-
areaFileReferences,
|
|
800
|
-
diagnostics
|
|
801
|
-
};
|
|
646
|
+
var renderConfig = (platforms = []) => {
|
|
647
|
+
const normalized = normalizePlatforms(platforms);
|
|
648
|
+
const config = createDefaultRawConfig();
|
|
649
|
+
if (normalized.length > 0) config.platforms = normalized;
|
|
650
|
+
return stringify(config);
|
|
651
|
+
};
|
|
652
|
+
var updateConfigPlatforms = (source, platforms) => {
|
|
653
|
+
const normalized = normalizePlatforms(platforms);
|
|
654
|
+
const parsed = parse2(source);
|
|
655
|
+
if (JSON.stringify(normalizePlatforms(parsed.platforms ?? [])) === JSON.stringify(normalized))
|
|
656
|
+
return source;
|
|
657
|
+
const document = parseDocument(source);
|
|
658
|
+
if (normalized.length === 0) document.delete("platforms");
|
|
659
|
+
else document.set("platforms", normalized);
|
|
660
|
+
return document.toString();
|
|
802
661
|
};
|
|
803
662
|
|
|
804
663
|
// src/truth/docs.ts
|
|
@@ -812,1521 +671,1605 @@ var resolveEngineeringTruthRoot = (config) => {
|
|
|
812
671
|
return config.truthmark.paths.engineeringTruthRoot;
|
|
813
672
|
};
|
|
814
673
|
|
|
815
|
-
// src/
|
|
816
|
-
var
|
|
817
|
-
return
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
674
|
+
// src/truth/evidence.ts
|
|
675
|
+
var renderClaimEvidenceCheckedSection = (items) => {
|
|
676
|
+
return [
|
|
677
|
+
"Evidence checked:",
|
|
678
|
+
...items.map((item) => {
|
|
679
|
+
return [
|
|
680
|
+
`- Claim: ${item.claim}`,
|
|
681
|
+
` Evidence: ${item.evidence.join(" / ")}`,
|
|
682
|
+
` Result: ${item.result}`
|
|
683
|
+
].join("\n");
|
|
684
|
+
})
|
|
685
|
+
].join("\n");
|
|
822
686
|
};
|
|
823
|
-
var
|
|
824
|
-
var renderLaneRootReadmeSummary = (lane) => {
|
|
825
|
-
if (lane === "product") {
|
|
826
|
-
return [
|
|
827
|
-
"Product truth owns capability promises, boundaries, decisions, and acceptance criteria.",
|
|
828
|
-
"Product lane docs state what must be true, why it matters, and what success means."
|
|
829
|
-
].join(" ");
|
|
830
|
-
}
|
|
687
|
+
var renderAuditEvidenceCheckedSection = (items) => {
|
|
831
688
|
return [
|
|
832
|
-
"
|
|
833
|
-
|
|
834
|
-
|
|
689
|
+
"Evidence checked:",
|
|
690
|
+
...items.map((item) => {
|
|
691
|
+
return [
|
|
692
|
+
`- Finding: ${item.finding}`,
|
|
693
|
+
` Evidence: ${item.evidence.join(" / ")}`,
|
|
694
|
+
` Suggested fix: ${item.suggestedFix}`,
|
|
695
|
+
` Confidence: ${item.confidence}`
|
|
696
|
+
].join("\n");
|
|
697
|
+
})
|
|
698
|
+
].join("\n");
|
|
835
699
|
};
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
return
|
|
700
|
+
|
|
701
|
+
// src/agents/shared.ts
|
|
702
|
+
var renderBulletLine = (line) => {
|
|
703
|
+
const normalized = line.trim().replace(/^-\s*/u, "");
|
|
704
|
+
return `- ${normalized}`;
|
|
841
705
|
};
|
|
842
|
-
var
|
|
843
|
-
return
|
|
706
|
+
var renderBulletBlock = (lines, indent = " ") => {
|
|
707
|
+
return lines.split(/\n/u).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => `${indent}${renderBulletLine(line)}`).join("\n");
|
|
844
708
|
};
|
|
845
|
-
var
|
|
846
|
-
|
|
709
|
+
var renderLaneClassificationRuleBlock = (config = defaultAgentConfig(), indent = " ") => {
|
|
710
|
+
const [, ...rules] = renderLaneClassificationInstructions(config).split(/\n/u);
|
|
711
|
+
return renderBulletBlock(rules.join("\n"), indent);
|
|
847
712
|
};
|
|
848
|
-
var
|
|
849
|
-
const
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
713
|
+
var renderReadOnlyLaneClassificationRuleBlock = (config = defaultAgentConfig(), indent = " ") => {
|
|
714
|
+
const productTruthRoot = resolveProductTruthRoot(config);
|
|
715
|
+
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
716
|
+
return renderBulletBlock(
|
|
717
|
+
[
|
|
718
|
+
"classify the request or changed surface as product-lane, engineering-lane, both-lane, or ambiguous for reporting only",
|
|
719
|
+
`product-lane ownership belongs under ${productTruthRoot} and describes product promises, boundaries, rationale, decisions, and success criteria`,
|
|
720
|
+
`engineering-lane ownership belongs under ${engineeringTruthRoot} and describes source-backed current realization, contracts, architecture, workflows, operations, or tests`,
|
|
721
|
+
"both-lane ownership uses separate product and engineering docs cross-linked in route YAML with realized_by and realizes, not in doc frontmatter",
|
|
722
|
+
"ambiguous lane ownership should be reported for manual handoff or routed to Truth Structure",
|
|
723
|
+
LANE_INVARIANT
|
|
724
|
+
].join("\n"),
|
|
725
|
+
indent
|
|
855
726
|
);
|
|
856
|
-
return [
|
|
857
|
-
"---",
|
|
858
|
-
"status: active",
|
|
859
|
-
"doc_type: route-index",
|
|
860
|
-
`last_reviewed: ${currentDate()}`,
|
|
861
|
-
"---",
|
|
862
|
-
"",
|
|
863
|
-
"# Truthmark Areas",
|
|
864
|
-
"",
|
|
865
|
-
`## ${title}`,
|
|
866
|
-
"",
|
|
867
|
-
"Area files:",
|
|
868
|
-
`- ${childPath}`,
|
|
869
|
-
"",
|
|
870
|
-
"Code surface:",
|
|
871
|
-
"- src/**",
|
|
872
|
-
"",
|
|
873
|
-
"Update truth when:",
|
|
874
|
-
"- behavior changes affect the routed truth documents",
|
|
875
|
-
"- API contracts or current feature behavior changes",
|
|
876
|
-
"",
|
|
877
|
-
"## Source References",
|
|
878
|
-
"",
|
|
879
|
-
`- ${sourceOfTruth}`,
|
|
880
|
-
""
|
|
881
|
-
].join("\n");
|
|
882
727
|
};
|
|
883
|
-
var
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
);
|
|
728
|
+
var DECISION_TRUTH_INSTRUCTIONS = [
|
|
729
|
+
"Decision truth lives in the canonical doc it governs; date active decisions inline when added or changed.",
|
|
730
|
+
"Do not create separate active-decision ADR/planning logs; replace the active decision and let Git history carry the audit trail.",
|
|
731
|
+
"Product decisions belong in product truth; engineering, architecture, contract, workflow, and operational decisions belong in engineering truth."
|
|
732
|
+
].join("\n");
|
|
733
|
+
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.";
|
|
734
|
+
var renderLaneClassificationInstructions = (config = defaultAgentConfig()) => {
|
|
735
|
+
const productTruthRoot = resolveProductTruthRoot(config);
|
|
736
|
+
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
893
737
|
return [
|
|
894
|
-
"
|
|
895
|
-
"
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
"
|
|
899
|
-
"",
|
|
900
|
-
|
|
901
|
-
"",
|
|
902
|
-
`## ${title}`,
|
|
903
|
-
"",
|
|
904
|
-
"Truth documents:",
|
|
905
|
-
"```yaml",
|
|
906
|
-
"truth_documents:",
|
|
907
|
-
` - path: ${bootstrapTruthDoc}`,
|
|
908
|
-
" kind: engineering-workflow",
|
|
909
|
-
" lane: engineering",
|
|
910
|
-
"```",
|
|
911
|
-
"",
|
|
912
|
-
"This is a provisional bootstrap route. It exists only to make fresh repositories routeable until real product, service, domain, or ownership areas are created.",
|
|
913
|
-
"",
|
|
914
|
-
"Code surface:",
|
|
915
|
-
"- src/**",
|
|
916
|
-
"",
|
|
917
|
-
"Update truth when:",
|
|
918
|
-
"- this provisional bootstrap route is the only match for a real code surface",
|
|
919
|
-
"- route ownership is still broad, mixed, or ambiguous",
|
|
920
|
-
"- Run Truth Structure before normal Truth Sync so the touched code gets a bounded owner",
|
|
921
|
-
"",
|
|
922
|
-
"## Source References",
|
|
923
|
-
"",
|
|
924
|
-
`- ${sourceOfTruth}`,
|
|
925
|
-
""
|
|
738
|
+
"Lane review questions:",
|
|
739
|
+
"- before writing canonical truth docs, classify the request or change as product-lane, engineering-lane, both-lane, or ambiguous",
|
|
740
|
+
`- product-lane writes belong under ${productTruthRoot} and state product promises, boundaries, rationale, decisions, and success criteria`,
|
|
741
|
+
`- engineering-lane writes belong under ${engineeringTruthRoot} and state source-backed current realization, contracts, architecture, workflows, operations, or tests`,
|
|
742
|
+
"- 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",
|
|
743
|
+
"- ambiguous lane ownership must stop or invoke Truth Structure instead of writing a mixed document",
|
|
744
|
+
`- ${LANE_INVARIANT}`
|
|
926
745
|
].join("\n");
|
|
927
746
|
};
|
|
928
|
-
var
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
"",
|
|
956
|
-
|
|
957
|
-
"
|
|
958
|
-
|
|
959
|
-
"
|
|
960
|
-
"",
|
|
961
|
-
"The scaffold creates this provisional bootstrap handoff only when a default broad route needs a canonical owner. Agents use it as a signal to run Truth Structure and create bounded routes before normal Truth Sync, not as a place to accumulate implementation claims.",
|
|
962
|
-
"",
|
|
963
|
-
"## Product Truth Links",
|
|
964
|
-
"",
|
|
965
|
-
"- None. This is an engineering bootstrap handoff for routing setup, not a product promise.",
|
|
966
|
-
"",
|
|
967
|
-
"## Triggers",
|
|
968
|
-
"",
|
|
969
|
-
"- A real code change maps only to this provisional broad route.",
|
|
970
|
-
"- Truth Sync cannot identify a specific behavior-owned route and bounded truth owner.",
|
|
971
|
-
"- A maintainer or agent is onboarding the first real product, service, domain, package, or ownership area.",
|
|
972
|
-
"",
|
|
973
|
-
"## Inputs",
|
|
974
|
-
"",
|
|
975
|
-
"- Current route files under the configured Truthmark route root.",
|
|
976
|
-
"- The touched code, tests, configuration, and existing docs needed to infer the smallest real owner.",
|
|
977
|
-
"- Repository instruction files that exist in the checkout.",
|
|
978
|
-
"",
|
|
979
|
-
"## Execution Model",
|
|
980
|
-
"",
|
|
981
|
-
"Run Truth Structure before normal Truth Sync when real code changes touch only this broad route. Truth Structure should create or repair bounded areas first; Truth Sync should then update the bounded owner docs.",
|
|
982
|
-
"",
|
|
983
|
-
"## Steps",
|
|
984
|
-
"",
|
|
985
|
-
"1. Treat this route as provisional and insufficient for normal behavior maintenance.",
|
|
986
|
-
"2. Inspect the touched code/test surface and infer the narrowest durable owner.",
|
|
987
|
-
"3. Create or repair route entries and truth docs for that owner.",
|
|
988
|
-
"4. Leave this bootstrap doc small; do not append behavior details here.",
|
|
989
|
-
"5. Resume Truth Sync only after the touched code resolves to a bounded owner.",
|
|
990
|
-
"",
|
|
991
|
-
"## State, Retry, And Failure Behavior",
|
|
992
|
-
"",
|
|
993
|
-
"If ownership cannot be inferred safely, stop and report manual-review files instead of widening this route or adding generic behavior prose.",
|
|
994
|
-
"",
|
|
995
|
-
"## Outputs",
|
|
996
|
-
"",
|
|
997
|
-
"- Bounded route areas and lane-appropriate truth docs for the touched surface.",
|
|
998
|
-
"- A compact manual handoff report when ownership remains ambiguous.",
|
|
999
|
-
"",
|
|
1000
|
-
"## Engineering Decisions",
|
|
1001
|
-
"",
|
|
1002
|
-
`- Decision (${today}): Default broad routing is provisional bootstrap state. Agents should create bounded areas before normal Truth Sync rather than extending a catch-all overview doc.`,
|
|
1003
|
-
"",
|
|
1004
|
-
"## Rationale",
|
|
1005
|
-
"",
|
|
1006
|
-
"Scoped ownership keeps agent context close to affected files and prevents broad default docs from absorbing unrelated behavior. This preserves agent-native truth maintenance without adding a token-heavy discovery layer.",
|
|
1007
|
-
"",
|
|
1008
|
-
"## Non-Goals",
|
|
1009
|
-
"",
|
|
1010
|
-
"- This doc is not a repository behavior overview.",
|
|
1011
|
-
"- This doc is not a product capability or engineering behavior owner.",
|
|
1012
|
-
"- This doc is not a permanent home for claims about files under `src/**`.",
|
|
1013
|
-
"",
|
|
1014
|
-
"## Maintenance Notes",
|
|
1015
|
-
"",
|
|
1016
|
-
"Keep this doc short. When a repository has real bounded routes, prefer updating those routes and their truth docs instead of expanding this bootstrap handoff.",
|
|
1017
|
-
"",
|
|
1018
|
-
"## Source References",
|
|
1019
|
-
"",
|
|
1020
|
-
`- ${routeSource}`,
|
|
1021
|
-
`- ${configSource}`,
|
|
1022
|
-
""
|
|
747
|
+
var EVIDENCE_AUTHORITY_INSTRUCTIONS = [
|
|
748
|
+
"Repository instruction files and explicitly configured policy docs remain instruction authority when present; do not assume a repository uses any particular policy path.",
|
|
749
|
+
"Implementation code and canonical truth docs are inspected evidence for current behavior; they do not silently override workflow write boundaries."
|
|
750
|
+
].join("\n");
|
|
751
|
+
var REPOSITORY_INTELLIGENCE_INSTRUCTIONS = [
|
|
752
|
+
"Repository intelligence artifacts are optional derived context: RepoIndex, RouteMap, ImpactSet, and WorkflowState/action context may guide routing, write boundaries, and verification planning when available.",
|
|
753
|
+
"They do not override checkout evidence, canonical truth docs, route files, or workflow write boundaries.",
|
|
754
|
+
"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."
|
|
755
|
+
].join("\n");
|
|
756
|
+
var FEATURE_DOC_TEMPLATE_INSTRUCTIONS = [
|
|
757
|
+
"When creating or updating a truth doc, inspect the routed truth kind and use the matching template under the configured Truthmark templates root.",
|
|
758
|
+
"Supported kinds: product-capability, engineering-behavior, engineering-contract, engineering-architecture, engineering-workflow, engineering-operations, and engineering-test-behavior.",
|
|
759
|
+
"Treat the HTML comments under each template section as normative authoring guidance for that section.",
|
|
760
|
+
"Align existing docs to that template and write or repair section content so it satisfies the comment guidance while preserving accurate authored content.",
|
|
761
|
+
"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.",
|
|
762
|
+
"Teams may edit template files under the configured Truthmark templates root to define their local truth-doc standards."
|
|
763
|
+
].join("\n");
|
|
764
|
+
var TRUTH_DOC_AUTHORING_STYLE_INSTRUCTIONS = [
|
|
765
|
+
"Truth-doc prose style:",
|
|
766
|
+
"- Use professional, plain technical prose. Prefer specific current-state claims over promotional, symbolic, or generic significance language.",
|
|
767
|
+
"- Avoid common AI-writing tells: pivotal, crucial, underscores, serves as, stands as, showcases, landscape, vague expert attributions, and generic upbeat conclusions.",
|
|
768
|
+
"- Keep claims evidence-backed and diff-friendly: one durable claim per bullet or line; paragraphs should be no longer than one or two short sentences.",
|
|
769
|
+
"- Do not add personality, rhetorical flourish, first-person commentary, or marketing tone.",
|
|
770
|
+
"- Rewrite dense or formulaic prose only when it improves readability without removing scope, evidence, decisions, or source references."
|
|
771
|
+
].join("\n");
|
|
772
|
+
var renderTruthDocOwnershipGateSection = (subject, outcome) => {
|
|
773
|
+
return [
|
|
774
|
+
"Truth-doc ownership review:",
|
|
775
|
+
`- before editing or relying on ${subject}, verify each target/source truth doc is a bounded owner for the behavior`,
|
|
776
|
+
"- if a target/source doc mixes independent owners, spans unrelated behaviors, acts as an index, or needs cross-owner edits, do not patch or in-place repair it",
|
|
777
|
+
`- ${outcome}`,
|
|
778
|
+
"- report Ownership reviewed, Structure required, Truth docs split, Truth docs restructured, or Manual handoff reason as applicable"
|
|
1023
779
|
].join("\n");
|
|
1024
780
|
};
|
|
1025
|
-
var
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
781
|
+
var TRUTH_DOC_DECISION_RATIONALE_PRESERVATION_INSTRUCTIONS = [
|
|
782
|
+
"Decision/Rationale preservation review:",
|
|
783
|
+
"- 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",
|
|
784
|
+
"- 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",
|
|
785
|
+
"- 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",
|
|
786
|
+
"- if ownership of a decision or rationale is unclear, stop with manual-review files instead of deleting it or guessing",
|
|
787
|
+
"- 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"
|
|
788
|
+
].join("\n");
|
|
789
|
+
var renderTruthDocRestructureGateSection = (scope) => {
|
|
1031
790
|
return [
|
|
1032
|
-
"
|
|
1033
|
-
|
|
1034
|
-
"
|
|
1035
|
-
|
|
1036
|
-
"
|
|
1037
|
-
"",
|
|
1038
|
-
"
|
|
1039
|
-
"",
|
|
1040
|
-
"This directory is an index for current truth docs organized by the configured Truthmark hierarchy.",
|
|
1041
|
-
"",
|
|
1042
|
-
renderLaneRootReadmeSummary(lane),
|
|
1043
|
-
"",
|
|
1044
|
-
renderLaneRootLeafDocGuidance(lane),
|
|
1045
|
-
"",
|
|
1046
|
-
"## Source References",
|
|
1047
|
-
"",
|
|
1048
|
-
`- ${sourceOfTruth}`,
|
|
1049
|
-
""
|
|
791
|
+
"Truth-doc shape repair review:",
|
|
792
|
+
`- ${scope}`,
|
|
793
|
+
"- repair shape in place only after the ownership review confirms the doc is the right bounded owner",
|
|
794
|
+
"- use Truth Structure for ownership splits; do not treat broad or mixed-owner docs as in-place repair work",
|
|
795
|
+
"- repair shape when a narrow edit would make truth worse: missing template sections, stale evidence conflicts, cross-section updates within one owner, or wrong frontmatter/source/headings",
|
|
796
|
+
"- preserve supported claims; remove, narrow, or record unsupported or stale claims for manual handoff",
|
|
797
|
+
"- report docs restructured and why a narrow edit was not sufficient"
|
|
1050
798
|
].join("\n");
|
|
1051
799
|
};
|
|
1052
|
-
var
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
templatePath,
|
|
1058
|
-
`${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`
|
|
1059
|
-
);
|
|
800
|
+
var ARCHITECTURE_DOC_BOUNDARY_INSTRUCTIONS = [
|
|
801
|
+
"Maintain architecture docs only for structure-level changes: system structure, module boundaries, runtime topology, persistence boundaries, cross-cutting contracts, or generated-surface ownership.",
|
|
802
|
+
"Keep ordinary behavior, endpoints, UI copy, validation rules, and bug fixes in behavior or contract docs unless they change those boundaries."
|
|
803
|
+
].join("\n");
|
|
804
|
+
var renderRouteFirstEvidenceGateSection = (subject, noImpactedDocOutcome) => {
|
|
1060
805
|
return [
|
|
1061
|
-
"
|
|
1062
|
-
|
|
1063
|
-
"
|
|
1064
|
-
|
|
1065
|
-
"
|
|
1066
|
-
"",
|
|
1067
|
-
|
|
1068
|
-
"",
|
|
1069
|
-
`This directory indexes bounded ${title.toLowerCase()} truth docs.`,
|
|
1070
|
-
"",
|
|
1071
|
-
"README.md files are indexes, not Truth Sync targets. Keep bounded truth in leaf docs in this directory.",
|
|
1072
|
-
"",
|
|
1073
|
-
"Current leaf docs:",
|
|
1074
|
-
"",
|
|
1075
|
-
"- [Bootstrap routing](bootstrap-routing.md)",
|
|
1076
|
-
"",
|
|
1077
|
-
"## Source References",
|
|
1078
|
-
"",
|
|
1079
|
-
`- ${sourceOfTruth}`,
|
|
1080
|
-
""
|
|
806
|
+
"Evidence checklist:",
|
|
807
|
+
`- route-first: map ${subject} to bounded route owners and primary canonical docs`,
|
|
808
|
+
"- review new or changed behavior-bearing claims only in touched docs, route ownership, lane-specific decisions, and rationale",
|
|
809
|
+
"- support claims with primary checkout evidence: implementation, config, routing, generated templates, schemas, or contract definitions",
|
|
810
|
+
"- tests/examples/canonical docs corroborate; they are not sole proof when implementation conflicts",
|
|
811
|
+
"- remove, narrow, or record unsupported claims for manual handoff",
|
|
812
|
+
`- ${noImpactedDocOutcome}`
|
|
1081
813
|
].join("\n");
|
|
1082
814
|
};
|
|
1083
|
-
var
|
|
815
|
+
var renderTopologyEvidenceGateSection = () => {
|
|
1084
816
|
return [
|
|
1085
|
-
|
|
1086
|
-
"",
|
|
1087
|
-
"
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
"",
|
|
1091
|
-
`{{${section.placeholder}}}`,
|
|
1092
|
-
""
|
|
1093
|
-
];
|
|
817
|
+
"Evidence checklist:",
|
|
818
|
+
"- apply the evidence checklist before finishing when Truth Structure writes routed docs, ownership claims, lane-specific decisions, or rationale",
|
|
819
|
+
"- support ownership/behavior claims with topology or primary checkout evidence from layout, implementation boundaries, docs, config, route files, tests, templates, schemas, or contracts",
|
|
820
|
+
"- tests/examples/canonical docs corroborate; remove, narrow, or record unsupported claims for manual handoff"
|
|
821
|
+
].join("\n");
|
|
1094
822
|
};
|
|
1095
|
-
var
|
|
1096
|
-
return
|
|
823
|
+
var renderAuditEvidenceGateSection = () => {
|
|
824
|
+
return [
|
|
825
|
+
"Evidence checklist:",
|
|
826
|
+
"- support each finding and suggested fix with evidence from config, route files, canonical docs, implementation, templates, or tests",
|
|
827
|
+
"- canonical docs are context, not sole proof when implementation conflicts",
|
|
828
|
+
"- remove unsupported findings or mark open questions; validate changed claims if you edit docs"
|
|
829
|
+
].join("\n");
|
|
1097
830
|
};
|
|
1098
|
-
var
|
|
1099
|
-
const
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
fencedCodeMarker = null;
|
|
1117
|
-
fencedCodeLength = 0;
|
|
1118
|
-
}
|
|
1119
|
-
continue;
|
|
1120
|
-
}
|
|
1121
|
-
if (fencedCodeMarker === null && /^## .+$/u.test(line)) {
|
|
1122
|
-
matches.push({ heading: line.trim(), index: lineMatch.index });
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
return matches;
|
|
831
|
+
var renderCodexSubagentModeSection = (agents, parentRule, writeAgents = []) => {
|
|
832
|
+
const writeAgentLines = writeAgents.length > 0 ? [
|
|
833
|
+
`- dispatch write-capable project agents only with explicit write leases: ${writeAgents.join(", ")}`,
|
|
834
|
+
"- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
|
|
835
|
+
"- write workers must stop when a required edit is off-lease and report status, filesChanged, evidence, offLeaseChanges, blockers, and notes",
|
|
836
|
+
"- parent must inspect the actual checkout diff against each lease before accepting a worker report"
|
|
837
|
+
] : [];
|
|
838
|
+
const readOnlyScope = writeAgents.length > 0 ? "for verification" : "only";
|
|
839
|
+
const readOnlyWorkerLabel = writeAgents.length > 0 ? "read-only workers" : "workers";
|
|
840
|
+
return [
|
|
841
|
+
"Codex subagent mode:",
|
|
842
|
+
"- use automatically when this workflow runs in Codex and the parent agent chooses bounded subagent fan-out",
|
|
843
|
+
`- dispatch read-only project agents ${readOnlyScope}: ${agents.join(", ")}`,
|
|
844
|
+
`- ${readOnlyWorkerLabel} inspect checkout evidence directly, return structured findings, and must not edit files`,
|
|
845
|
+
`- parent supplies bounded evidence shards; ${readOnlyWorkerLabel} must not preload host instruction files or repo-wide policy docs unless assigned as evidence`,
|
|
846
|
+
...writeAgentLines,
|
|
847
|
+
`- ${parentRule}`
|
|
848
|
+
].join("\n");
|
|
1126
849
|
};
|
|
1127
|
-
var
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
850
|
+
var renderOpenCodeSubagentModeSection = (agents, parentRule, writeAgents = []) => {
|
|
851
|
+
const mentions = agents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
|
|
852
|
+
const writeMentions = writeAgents.map(
|
|
853
|
+
(agent) => `@${agent.replace(/_/gu, "-")}`
|
|
854
|
+
);
|
|
855
|
+
const writeAgentLines = writeMentions.length > 0 ? [
|
|
856
|
+
`- dispatch write-capable project subagents only with explicit write leases: ${writeMentions.join(", ")}`,
|
|
857
|
+
"- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
|
|
858
|
+
"- write workers must stop when a required edit is off-lease and report status, filesChanged, evidence, offLeaseChanges, blockers, and notes",
|
|
859
|
+
"- parent must inspect the actual checkout diff against each lease before accepting a worker report"
|
|
860
|
+
] : [];
|
|
861
|
+
const readOnlyScope = writeAgents.length > 0 ? "for verification" : "only";
|
|
862
|
+
const readOnlyWorkerLabel = writeAgents.length > 0 ? "read-only workers" : "workers";
|
|
863
|
+
return [
|
|
864
|
+
"OpenCode subagent mode:",
|
|
865
|
+
"- use automatically when this workflow runs in OpenCode and the parent agent chooses bounded subagent fan-out",
|
|
866
|
+
`- dispatch read-only project subagents ${readOnlyScope}: ${mentions.join(", ")}`,
|
|
867
|
+
`- ${readOnlyWorkerLabel} inspect checkout evidence directly, return structured findings, and must not edit files`,
|
|
868
|
+
`- parent supplies bounded evidence shards; ${readOnlyWorkerLabel} must not preload host instruction files or repo-wide policy docs unless assigned as evidence`,
|
|
869
|
+
...writeAgentLines,
|
|
870
|
+
`- ${parentRule}`
|
|
871
|
+
].join("\n");
|
|
872
|
+
};
|
|
873
|
+
var renderClaudeSubagentModeSection = (agents, parentRule, writeAgents = []) => {
|
|
874
|
+
const mentions = agents.map(
|
|
875
|
+
(agent) => `${agent.replace(/_/gu, "-")} subagent`
|
|
876
|
+
);
|
|
877
|
+
const writeMentions = writeAgents.map(
|
|
878
|
+
(agent) => `${agent.replace(/_/gu, "-")} subagent`
|
|
879
|
+
);
|
|
880
|
+
const writeAgentLines = writeMentions.length > 0 ? [
|
|
881
|
+
`- dispatch write-capable project subagents only with explicit write leases: ${writeMentions.join(", ")}`,
|
|
882
|
+
"- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
|
|
883
|
+
"- write workers must stop when a required edit is off-lease and report status, filesChanged, evidence, offLeaseChanges, blockers, and notes",
|
|
884
|
+
"- parent must inspect the actual checkout diff against each lease before accepting a worker report"
|
|
885
|
+
] : [];
|
|
886
|
+
const readOnlyScope = writeAgents.length > 0 ? "for verification" : "only";
|
|
887
|
+
const readOnlySubagentLabel = writeAgents.length > 0 ? "read-only subagents" : "subagents";
|
|
888
|
+
return [
|
|
889
|
+
"Claude Code subagent mode:",
|
|
890
|
+
"- use automatically when this workflow runs in Claude Code and the parent agent chooses bounded subagent fan-out",
|
|
891
|
+
`- dispatch read-only project subagents ${readOnlyScope}: ${mentions.join(", ")}`,
|
|
892
|
+
`- ${readOnlySubagentLabel} inspect checkout evidence directly, return structured findings, and must not edit files`,
|
|
893
|
+
`- parent supplies bounded evidence shards; ${readOnlySubagentLabel} must not preload host instruction files or repo-wide policy docs unless assigned as evidence`,
|
|
894
|
+
...writeAgentLines,
|
|
895
|
+
`- ${parentRule}`
|
|
896
|
+
].join("\n");
|
|
897
|
+
};
|
|
898
|
+
var renderCopilotCustomAgentModeSection = (agents, parentRule, writeAgents = []) => {
|
|
899
|
+
const mentions = agents.map((agent) => `@${agent.replace(/_/gu, "-")}`);
|
|
900
|
+
const writeMentions = writeAgents.map(
|
|
901
|
+
(agent) => `@${agent.replace(/_/gu, "-")}`
|
|
902
|
+
);
|
|
903
|
+
const writeAgentLines = writeMentions.length > 0 ? [
|
|
904
|
+
`- dispatch write-capable project custom agents only with explicit write leases: ${writeMentions.join(", ")}`,
|
|
905
|
+
"- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
|
|
906
|
+
"- write workers must stop when a required edit is off-lease and report status, filesChanged, evidence, offLeaseChanges, blockers, and notes",
|
|
907
|
+
"- parent must inspect the actual checkout diff against each lease before accepting a worker report"
|
|
908
|
+
] : [];
|
|
909
|
+
const readOnlyScope = writeAgents.length > 0 ? "for verification" : "only";
|
|
910
|
+
const readOnlyCustomAgentLabel = writeAgents.length > 0 ? "read-only custom agents" : "custom agents";
|
|
911
|
+
return [
|
|
912
|
+
"Copilot custom-agent mode:",
|
|
913
|
+
"- use automatically when this workflow runs in Copilot and the parent agent chooses bounded custom-agent fan-out",
|
|
914
|
+
`- dispatch read-only project custom agents ${readOnlyScope}: ${mentions.join(", ")}`,
|
|
915
|
+
`- ${readOnlyCustomAgentLabel} inspect checkout evidence directly, return structured findings, and must not edit files`,
|
|
916
|
+
`- parent supplies bounded evidence shards; ${readOnlyCustomAgentLabel} must not preload host instruction files or repo-wide policy docs unless assigned as evidence`,
|
|
917
|
+
...writeAgentLines,
|
|
918
|
+
`- ${parentRule}`
|
|
919
|
+
].join("\n");
|
|
920
|
+
};
|
|
921
|
+
var defaultAgentConfig = () => {
|
|
922
|
+
return createDefaultConfig();
|
|
923
|
+
};
|
|
924
|
+
var renderHierarchySummary = (config) => {
|
|
925
|
+
const productRoot = resolveProductTruthRoot(config);
|
|
926
|
+
const engineeringRoot = resolveEngineeringTruthRoot(config);
|
|
927
|
+
return [
|
|
928
|
+
"Truthmark hierarchy hints:",
|
|
929
|
+
"- Config, when present: .truthmark/config.yml",
|
|
930
|
+
`- Root route index, when present: ${config.truthmark.paths.routesIndex}`,
|
|
931
|
+
`- Area route files, when present: ${config.truthmark.paths.routeAreasRoot}/**/*.md`,
|
|
932
|
+
`- Product truth docs, when present: ${productRoot}/**/*.md`,
|
|
933
|
+
`- Engineering truth docs, when present: ${engineeringRoot}/**/*.md`
|
|
934
|
+
].join("\n");
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
// src/templates/agents-block.ts
|
|
938
|
+
var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
|
|
939
|
+
var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
|
|
940
|
+
var renderCompactHierarchySummary = (config) => {
|
|
941
|
+
const productTruthRoot = resolveProductTruthRoot(config);
|
|
942
|
+
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
943
|
+
const truthDocRoots = Array.from(
|
|
944
|
+
/* @__PURE__ */ new Set([productTruthRoot, engineeringTruthRoot])
|
|
945
|
+
).map((truthRoot3) => `${truthRoot3}/**/*.md`);
|
|
946
|
+
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.`;
|
|
947
|
+
};
|
|
948
|
+
var renderAgentsBlock = (config = defaultAgentConfig()) => {
|
|
949
|
+
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;
|
|
950
|
+
return [
|
|
951
|
+
TRUTHMARK_BLOCK_START,
|
|
952
|
+
"## Truthmark Workflow",
|
|
953
|
+
"",
|
|
954
|
+
"Truthmark-managed block. Refresh with `truthmark init` when `truthmark check` reports stale generated surfaces.",
|
|
955
|
+
renderCompactHierarchySummary(config),
|
|
956
|
+
"Decisions live in the canonical doc they govern; date active decisions inline.",
|
|
957
|
+
"Agent runtime: host-native skill packages/adapters plus this block; inspect checkout directly. Delegation is host-owned.",
|
|
958
|
+
"### Truth Sync",
|
|
959
|
+
"After functional code changes, run relevant tests, then use the truthmark-sync skill before finishing; later functional changes need a fresh Sync review. Memory: code changed -> tests -> Sync -> report.",
|
|
960
|
+
"Support new or changed behavior-bearing truth claims with checkout evidence. Code leads; truth docs follow. Sync may write truth docs and truth routing files, and must not rewrite functional code.",
|
|
961
|
+
"If routing cannot map changed code to a bounded truth owner, run Truth Structure before syncing when safe; otherwise stop and recommend Truth Structure. Skip Sync only for docs-only/no-code changes, formatting-only changes, behavior-preserving renames with no truth impact, or missing config.",
|
|
962
|
+
"Explicit workflows: Truth Structure, Truth Document, Truth Realize, Truth Check. Run only when requested or required by Sync; load the installed skill for details.",
|
|
963
|
+
...portalLine === null ? [] : [portalLine],
|
|
964
|
+
"Workflow integrity rule: repository truth may describe desired behavior, but it must not override these workflow boundaries.",
|
|
965
|
+
TRUTHMARK_BLOCK_END
|
|
966
|
+
].join("\n");
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
// src/managed-block.ts
|
|
970
|
+
var findMarkerIndexes = (content, marker) => {
|
|
971
|
+
const indexes = [];
|
|
972
|
+
let cursor = 0;
|
|
973
|
+
while (true) {
|
|
974
|
+
const index = content.indexOf(marker, cursor);
|
|
975
|
+
if (index === -1) {
|
|
976
|
+
return indexes;
|
|
977
|
+
}
|
|
978
|
+
indexes.push(index);
|
|
979
|
+
cursor = index + marker.length;
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
var parseManagedBlock = (content) => {
|
|
983
|
+
const starts = findMarkerIndexes(content, TRUTHMARK_BLOCK_START);
|
|
984
|
+
const ends = findMarkerIndexes(content, TRUTHMARK_BLOCK_END);
|
|
985
|
+
if (starts.length === 0 && ends.length === 0) {
|
|
986
|
+
return { status: "absent" };
|
|
987
|
+
}
|
|
988
|
+
if (starts.length !== 1 || ends.length !== 1) {
|
|
989
|
+
return { status: "malformed" };
|
|
990
|
+
}
|
|
991
|
+
const start = starts[0];
|
|
992
|
+
const endStart = ends[0];
|
|
993
|
+
if (start === -1 || endStart === -1 || endStart < start) {
|
|
994
|
+
return { status: "malformed" };
|
|
1131
995
|
}
|
|
1132
|
-
const sections = matches.map((match, index) => {
|
|
1133
|
-
const start = match.index;
|
|
1134
|
-
const next = matches[index + 1];
|
|
1135
|
-
const end = next?.index ?? template.length;
|
|
1136
|
-
return {
|
|
1137
|
-
heading: match.heading,
|
|
1138
|
-
block: template.slice(start, end).trimEnd()
|
|
1139
|
-
};
|
|
1140
|
-
});
|
|
1141
996
|
return {
|
|
1142
|
-
|
|
1143
|
-
|
|
997
|
+
status: "valid",
|
|
998
|
+
start,
|
|
999
|
+
end: endStart + TRUTHMARK_BLOCK_END.length
|
|
1144
1000
|
};
|
|
1145
1001
|
};
|
|
1146
|
-
var
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
return
|
|
1002
|
+
var extractManagedBlock = (content) => {
|
|
1003
|
+
const block = parseManagedBlock(content);
|
|
1004
|
+
if (block.status !== "valid") {
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
return content.slice(block.start, block.end);
|
|
1152
1008
|
};
|
|
1153
|
-
var
|
|
1154
|
-
|
|
1155
|
-
|
|
1009
|
+
var trimmedBoundary = (value) => value.replace(/\n+$/u, "");
|
|
1010
|
+
var upsertManagedBlock = (existingContent, block) => {
|
|
1011
|
+
if (existingContent === null || existingContent.trim().length === 0) {
|
|
1012
|
+
return block;
|
|
1156
1013
|
}
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
return preamble;
|
|
1014
|
+
const marker = parseManagedBlock(existingContent);
|
|
1015
|
+
if (marker.status !== "valid") {
|
|
1016
|
+
return `${trimmedBoundary(existingContent)}
|
|
1017
|
+
|
|
1018
|
+
${block}`;
|
|
1163
1019
|
}
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
const keyMatch = /^([A-Za-z0-9_-]+):(\s|$)/u.exec(line);
|
|
1169
|
-
if (keyMatch) {
|
|
1170
|
-
skippingManagedField = fieldsToRemove.has(keyMatch[1] ?? "");
|
|
1171
|
-
}
|
|
1172
|
-
if (!skippingManagedField) {
|
|
1173
|
-
keptFrontmatterLines.push(line);
|
|
1174
|
-
}
|
|
1020
|
+
const before = trimmedBoundary(existingContent.slice(0, marker.start));
|
|
1021
|
+
const after = existingContent.slice(marker.end).replace(/^\n+/u, "");
|
|
1022
|
+
if (before.length === 0 && after.length === 0) {
|
|
1023
|
+
return block;
|
|
1175
1024
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1025
|
+
if (before.length === 0) {
|
|
1026
|
+
return `${block}
|
|
1027
|
+
|
|
1028
|
+
${after}`;
|
|
1029
|
+
}
|
|
1030
|
+
if (after.length === 0) {
|
|
1031
|
+
return `${before}
|
|
1032
|
+
|
|
1033
|
+
${block}`;
|
|
1034
|
+
}
|
|
1035
|
+
return `${before}
|
|
1036
|
+
|
|
1037
|
+
${block}
|
|
1038
|
+
|
|
1039
|
+
${after}`;
|
|
1182
1040
|
};
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1041
|
+
|
|
1042
|
+
// src/git/repository.ts
|
|
1043
|
+
import fs3 from "fs/promises";
|
|
1044
|
+
import { realpathSync } from "fs";
|
|
1045
|
+
import path3 from "path";
|
|
1046
|
+
import { execa } from "execa";
|
|
1047
|
+
var realpathOrResolved = async (targetPath) => {
|
|
1048
|
+
try {
|
|
1049
|
+
return await fs3.realpath(targetPath);
|
|
1050
|
+
} catch {
|
|
1051
|
+
return path3.resolve(targetPath);
|
|
1186
1052
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1053
|
+
};
|
|
1054
|
+
var runGit = async (cwd, args, reject = true) => {
|
|
1055
|
+
const result = await execa("git", args, { cwd, reject });
|
|
1056
|
+
return {
|
|
1057
|
+
stdout: result.stdout,
|
|
1058
|
+
exitCode: result.exitCode ?? 1
|
|
1059
|
+
};
|
|
1060
|
+
};
|
|
1061
|
+
var getGitRepository = async (cwd) => {
|
|
1062
|
+
const worktreePath = await realpathOrResolved(
|
|
1063
|
+
(await runGit(cwd, ["rev-parse", "--show-toplevel"])).stdout.trim()
|
|
1191
1064
|
);
|
|
1192
|
-
const
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
}
|
|
1210
|
-
trailingCustomSections.push(section);
|
|
1211
|
-
});
|
|
1212
|
-
const mergedSections = defaultParsed.sections.flatMap((section) => [
|
|
1213
|
-
...customBeforeDefault.get(section.heading) ?? [],
|
|
1214
|
-
section
|
|
1215
|
-
]);
|
|
1216
|
-
return [
|
|
1217
|
-
stripManagedFrontmatterFields(existingParsed.preamble),
|
|
1218
|
-
...mergedSections.map((section) => section.block),
|
|
1219
|
-
...trailingCustomSections.map((section) => section.block),
|
|
1220
|
-
""
|
|
1221
|
-
].filter((block) => block.length > 0).join("\n\n");
|
|
1222
|
-
};
|
|
1223
|
-
var renderBehaviorDocTemplateFile = () => {
|
|
1224
|
-
return [
|
|
1225
|
-
"---",
|
|
1226
|
-
"status: active",
|
|
1227
|
-
"truth_kind: engineering-behavior",
|
|
1228
|
-
`last_reviewed: ${currentDate()}`,
|
|
1229
|
-
"---",
|
|
1230
|
-
"",
|
|
1231
|
-
"# {{title}}",
|
|
1232
|
-
"",
|
|
1233
|
-
"## Purpose",
|
|
1234
|
-
"",
|
|
1235
|
-
"<!--",
|
|
1236
|
-
"State the user/system outcome this behavior protects and why it exists.",
|
|
1237
|
-
"Include the problem boundary and durable value; exclude roadmap, implementation plan, and historical narrative.",
|
|
1238
|
-
"List the code, config, docs, or tests that support the claim in Source References rather than prose-only assertion.",
|
|
1239
|
-
"-->",
|
|
1240
|
-
"",
|
|
1241
|
-
"{{purpose}}",
|
|
1242
|
-
"",
|
|
1243
|
-
"## Scope",
|
|
1244
|
-
"",
|
|
1245
|
-
"<!--",
|
|
1246
|
-
"Define the one coherent behavior surface this document owns.",
|
|
1247
|
-
"Include in-scope actors, entrypoints, state/data owned by this doc, and explicit handoffs to neighboring truth docs.",
|
|
1248
|
-
"Split into another leaf doc when content introduces a distinct outcome, state machine, rule family, external contract, or route owner.",
|
|
1249
|
-
"Keep README.md files as indexes only.",
|
|
1250
|
-
"-->",
|
|
1251
|
-
"",
|
|
1252
|
-
"{{scope}}",
|
|
1253
|
-
"",
|
|
1254
|
-
"This doc was created from the editable engineering-behavior template at {{template_path}}.",
|
|
1255
|
-
"",
|
|
1256
|
-
"## Current Implementation Behavior",
|
|
1257
|
-
"",
|
|
1258
|
-
"<!--",
|
|
1259
|
-
"Describe only current implemented behavior in present tense.",
|
|
1260
|
-
"Cover observable behavior, important defaults, and user/system-visible effects; exclude desired future behavior and speculative design.",
|
|
1261
|
-
"Every non-obvious claim should be checkable from Source References.",
|
|
1262
|
-
"-->",
|
|
1263
|
-
"",
|
|
1264
|
-
"{{current_implementation_behavior}}",
|
|
1265
|
-
"",
|
|
1266
|
-
"## Core Rules",
|
|
1267
|
-
"",
|
|
1268
|
-
"<!--",
|
|
1269
|
-
"Capture stable business rules, invariants, precedence rules, validation rules, and must-never constraints.",
|
|
1270
|
-
"Separate rules from incidental implementation details; cite current implementation or tests for rule enforcement.",
|
|
1271
|
-
"-->",
|
|
1272
|
-
"",
|
|
1273
|
-
"{{core_rules}}",
|
|
1274
|
-
"",
|
|
1275
|
-
"## Behavior Scenarios",
|
|
1276
|
-
"",
|
|
1277
|
-
"<!--",
|
|
1278
|
-
"Use compact scenario blocks only where they clarify normal, fallback, or compatibility-critical behavior.",
|
|
1279
|
-
"Write scenarios as current truth, not desired requirements: `#### Scenario: <implemented case>` followed by `- **GIVEN** ...`, `- **WHEN** ...`, `- **THEN** ...`, and optional `- **AND** ...` bullets.",
|
|
1280
|
-
"Keep each bullet evidence-backed and observable; do not force a scenario for every rule.",
|
|
1281
|
-
"-->",
|
|
1282
|
-
"",
|
|
1283
|
-
"{{behavior_scenarios}}",
|
|
1284
|
-
"",
|
|
1285
|
-
"## Flows And States",
|
|
1286
|
-
"",
|
|
1287
|
-
"<!--",
|
|
1288
|
-
"Document state transitions, lifecycle stages, retries, fallbacks, route switches, and important error paths.",
|
|
1289
|
-
"State 'None beyond current behavior.' when this behavior has no distinct flow or state model.",
|
|
1290
|
-
"-->",
|
|
1291
|
-
"",
|
|
1292
|
-
"{{flows_and_states}}",
|
|
1293
|
-
"",
|
|
1294
|
-
"## Contracts",
|
|
1295
|
-
"",
|
|
1296
|
-
"<!--",
|
|
1297
|
-
"Capture user-visible or integration contracts: CLI/API shape, inputs, outputs, diagnostics, files, events, permissions, or links to canonical contract docs.",
|
|
1298
|
-
"Avoid duplicating a separate canonical contract doc; link to it when contract ownership lives elsewhere.",
|
|
1299
|
-
"-->",
|
|
1300
|
-
"",
|
|
1301
|
-
"{{contracts}}",
|
|
1302
|
-
"",
|
|
1303
|
-
"## Product Truth Links",
|
|
1304
|
-
"",
|
|
1305
|
-
"<!--",
|
|
1306
|
-
"List product truth docs this engineering doc realizes; author canonical realizes links in route YAML, not doc frontmatter.",
|
|
1307
|
-
"Use 'None.' when this is purely internal engineering behavior.",
|
|
1308
|
-
"-->",
|
|
1309
|
-
"",
|
|
1310
|
-
"{{product_truth_links}}",
|
|
1311
|
-
"",
|
|
1312
|
-
"## Engineering Decisions",
|
|
1313
|
-
"",
|
|
1314
|
-
"<!--",
|
|
1315
|
-
"Keep active decisions only, dated inline when added or changed.",
|
|
1316
|
-
"Explain decisions that shape behavior, boundaries, rejected alternatives, or migration constraints; replace stale decisions instead of appending historical logs.",
|
|
1317
|
-
"-->",
|
|
1318
|
-
"",
|
|
1319
|
-
"{{engineering_decisions}}",
|
|
1320
|
-
"",
|
|
1321
|
-
"## Rationale",
|
|
1322
|
-
"",
|
|
1323
|
-
"<!--",
|
|
1324
|
-
"Explain why the current behavior and active decisions are this way, including tradeoffs and constraints.",
|
|
1325
|
-
"Tie rationale to evidence-backed behavior; do not use this as a changelog.",
|
|
1326
|
-
"-->",
|
|
1327
|
-
"",
|
|
1328
|
-
"{{rationale}}",
|
|
1329
|
-
"",
|
|
1330
|
-
"## Non-Goals",
|
|
1331
|
-
"",
|
|
1332
|
-
"<!--",
|
|
1333
|
-
"Name adjacent behavior this doc intentionally does not own, especially tempting future expansions or neighboring route owners.",
|
|
1334
|
-
"Use this section to prevent scope creep and duplicate truth ownership.",
|
|
1335
|
-
"-->",
|
|
1336
|
-
"",
|
|
1337
|
-
"{{non_goals}}",
|
|
1338
|
-
"",
|
|
1339
|
-
"## Maintenance Notes",
|
|
1340
|
-
"",
|
|
1341
|
-
"<!--",
|
|
1342
|
-
"List related tests, routing cautions, migration notes, evidence drift risks, and review triggers for future maintainers or agents.",
|
|
1343
|
-
"Keep this operational and current-state focused, not historical.",
|
|
1344
|
-
"-->",
|
|
1345
|
-
"",
|
|
1346
|
-
"{{maintenance_notes}}",
|
|
1347
|
-
"",
|
|
1348
|
-
"## Source References",
|
|
1349
|
-
"",
|
|
1350
|
-
"<!--",
|
|
1351
|
-
"List source files, tests, configs, generated templates, route files, or product instructions that support current claims.",
|
|
1352
|
-
"-->",
|
|
1353
|
-
"",
|
|
1354
|
-
"{{source_references}}",
|
|
1355
|
-
""
|
|
1356
|
-
].join("\n");
|
|
1357
|
-
};
|
|
1358
|
-
var sectionSpec = (heading, guidance, placeholder = titleToPlaceholder(heading)) => ({ heading, guidance, placeholder });
|
|
1359
|
-
var PURPOSE_SECTION = sectionSpec("## Purpose", [
|
|
1360
|
-
"State the software-engineering outcome this document protects and why the documented surface exists.",
|
|
1361
|
-
"Include durable value, impacted users/systems, and the problem boundary; exclude roadmap, implementation plans, and historical narrative.",
|
|
1362
|
-
"Keep claims traceable to Source References rather than prose-only assertion."
|
|
1363
|
-
]);
|
|
1364
|
-
var SCOPE_SECTION = sectionSpec("## Scope", [
|
|
1365
|
-
"Define the one coherent surface this document owns, including actors, entrypoints, owned state/data, and handoffs to neighboring truth docs.",
|
|
1366
|
-
"Call out important out-of-scope boundaries here or in Non-Goals; split the doc when it mixes distinct outcomes, lifecycles, contracts, or owners."
|
|
1367
|
-
]);
|
|
1368
|
-
var PRODUCT_DECISIONS_SECTION = sectionSpec(
|
|
1369
|
-
"## Product Decisions",
|
|
1370
|
-
[
|
|
1371
|
-
"Keep active decisions only, dated inline when added or changed.",
|
|
1372
|
-
"Capture decisions that shape behavior, interfaces, boundaries, compatibility, risk acceptance, or migration constraints.",
|
|
1373
|
-
"Replace stale decisions instead of appending historical logs."
|
|
1374
|
-
],
|
|
1375
|
-
"decision"
|
|
1376
|
-
);
|
|
1377
|
-
var ENGINEERING_DECISIONS_SECTION = sectionSpec(
|
|
1378
|
-
"## Engineering Decisions",
|
|
1379
|
-
[
|
|
1380
|
-
"Keep active engineering, architecture, contract, workflow, or operational decisions only, dated inline when added or changed.",
|
|
1381
|
-
"Do not restate product promises, product rationale, or business decisions here; link product truth instead.",
|
|
1382
|
-
"Replace stale decisions instead of appending historical logs."
|
|
1383
|
-
],
|
|
1384
|
-
"engineering_decisions"
|
|
1385
|
-
);
|
|
1386
|
-
var RATIONALE_SECTION = sectionSpec("## Rationale", [
|
|
1387
|
-
"Explain why the current behavior, structure, or contract is this way, including tradeoffs and constraints.",
|
|
1388
|
-
"Tie rationale to evidence-backed facts and active decisions; do not use this as a changelog."
|
|
1389
|
-
]);
|
|
1390
|
-
var NON_GOALS_SECTION = sectionSpec("## Non-Goals", [
|
|
1391
|
-
"Name adjacent behavior, responsibilities, interfaces, or future expansions this doc intentionally does not own.",
|
|
1392
|
-
"Use this section to prevent scope creep and duplicate truth ownership."
|
|
1393
|
-
]);
|
|
1394
|
-
var MAINTENANCE_NOTES_SECTION = sectionSpec("## Maintenance Notes", [
|
|
1395
|
-
"List related tests, routing cautions, migration notes, compatibility risks, evidence drift risks, and review triggers for future maintainers or agents.",
|
|
1396
|
-
"Keep this operational and current-state focused, not historical."
|
|
1397
|
-
]);
|
|
1398
|
-
var SOURCE_REFERENCES_SECTION = sectionSpec(
|
|
1399
|
-
"## Source References",
|
|
1400
|
-
[
|
|
1401
|
-
"List source files, tests, configs, generated templates, route files, or product instructions that support current claims."
|
|
1402
|
-
],
|
|
1403
|
-
"source_references"
|
|
1404
|
-
);
|
|
1405
|
-
var renderTypedTruthDocTemplate = (truthKind, title, sections) => {
|
|
1406
|
-
return [
|
|
1407
|
-
"---",
|
|
1408
|
-
"status: active",
|
|
1409
|
-
`truth_kind: ${truthKind}`,
|
|
1410
|
-
`last_reviewed: ${currentDate()}`,
|
|
1411
|
-
"---",
|
|
1412
|
-
"",
|
|
1413
|
-
`# ${title}`,
|
|
1414
|
-
"",
|
|
1415
|
-
...renderTemplateSection(PURPOSE_SECTION),
|
|
1416
|
-
...renderTemplateSection(SCOPE_SECTION),
|
|
1417
|
-
...sections.flatMap(renderTemplateSection),
|
|
1418
|
-
...renderTemplateSection(ENGINEERING_DECISIONS_SECTION),
|
|
1419
|
-
...renderTemplateSection(RATIONALE_SECTION),
|
|
1420
|
-
...renderTemplateSection(NON_GOALS_SECTION),
|
|
1421
|
-
...renderTemplateSection(MAINTENANCE_NOTES_SECTION),
|
|
1422
|
-
...renderTemplateSection(SOURCE_REFERENCES_SECTION)
|
|
1423
|
-
].join("\n");
|
|
1424
|
-
};
|
|
1425
|
-
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.";
|
|
1426
|
-
var renderProductTruthDocTemplate = (truthKind, title, sections, includeNonGoals) => {
|
|
1427
|
-
return [
|
|
1428
|
-
"---",
|
|
1429
|
-
"status: active",
|
|
1430
|
-
`truth_kind: ${truthKind}`,
|
|
1431
|
-
`last_reviewed: ${currentDate()}`,
|
|
1432
|
-
"---",
|
|
1433
|
-
"",
|
|
1434
|
-
`# ${title}`,
|
|
1435
|
-
"",
|
|
1436
|
-
"<!--",
|
|
1437
|
-
CORE_LANE_INVARIANT,
|
|
1438
|
-
"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.",
|
|
1439
|
-
"-->",
|
|
1440
|
-
"",
|
|
1441
|
-
...sections.flatMap(renderTemplateSection),
|
|
1442
|
-
...renderTemplateSection(PRODUCT_DECISIONS_SECTION),
|
|
1443
|
-
...renderTemplateSection(
|
|
1444
|
-
sectionSpec(
|
|
1445
|
-
"## Engineering Realization Links",
|
|
1446
|
-
[
|
|
1447
|
-
"List engineering truth that realizes this product truth; author canonical realized_by links in route YAML, not doc frontmatter.",
|
|
1448
|
-
"Do not summarize those engineering docs."
|
|
1449
|
-
],
|
|
1450
|
-
"engineering_realization_links"
|
|
1451
|
-
)
|
|
1452
|
-
),
|
|
1453
|
-
...includeNonGoals ? renderTemplateSection(NON_GOALS_SECTION) : [],
|
|
1454
|
-
...renderTemplateSection(SOURCE_REFERENCES_SECTION)
|
|
1455
|
-
].join("\n");
|
|
1456
|
-
};
|
|
1457
|
-
var renderProductCapabilityDocTemplateFile = () => {
|
|
1458
|
-
return renderProductTruthDocTemplate(
|
|
1459
|
-
"product-capability",
|
|
1460
|
-
"{{title}}",
|
|
1461
|
-
[
|
|
1462
|
-
sectionSpec("## Capability Promise", [
|
|
1463
|
-
"State the single user-visible capability and what must be true for users or stakeholders.",
|
|
1464
|
-
"Do not describe implementation mechanics here."
|
|
1465
|
-
]),
|
|
1466
|
-
sectionSpec("## Users And Value", [
|
|
1467
|
-
"Describe who benefits from the capability and the durable value it protects.",
|
|
1468
|
-
"Tie claims to repository evidence, explicit user instruction, or current behavior."
|
|
1469
|
-
]),
|
|
1470
|
-
sectionSpec("## Capability Scope", [
|
|
1471
|
-
"Define what this capability includes and excludes, including product boundary constraints and adjacent systems.",
|
|
1472
|
-
"Capture important scope limits, ownership boundaries, and non-goal pointers here; keep technical contracts in engineering truth."
|
|
1473
|
-
]),
|
|
1474
|
-
sectionSpec("## Current Product Behavior", [
|
|
1475
|
-
"Describe current implemented user-visible behavior in present tense.",
|
|
1476
|
-
"Code files may appear in Source References when they directly prove current behavior."
|
|
1477
|
-
]),
|
|
1478
|
-
sectionSpec("## Acceptance Criteria", [
|
|
1479
|
-
"List observable criteria that show the capability promise is currently satisfied.",
|
|
1480
|
-
"Include criteria that review whether the capability stays within its stated scope and boundary.",
|
|
1481
|
-
"Use criteria that can be reviewed from repository evidence or explicit product instruction."
|
|
1482
|
-
])
|
|
1483
|
-
],
|
|
1484
|
-
true
|
|
1485
|
-
);
|
|
1486
|
-
};
|
|
1487
|
-
var renderContractDocTemplateFile = () => {
|
|
1488
|
-
return renderTypedTruthDocTemplate("engineering-contract", "{{title}}", [
|
|
1489
|
-
sectionSpec("## Contract Surface", [
|
|
1490
|
-
"Identify the owned API, CLI, file format, event, protocol, permission boundary, or integration surface.",
|
|
1491
|
-
"State consumers/producers, stability level, and the source files/tests that define the contract."
|
|
1492
|
-
]),
|
|
1493
|
-
sectionSpec("## Inputs", [
|
|
1494
|
-
"Document accepted parameters, payloads, files, environment/config keys, permissions, and validation rules.",
|
|
1495
|
-
"Include required/optional status, defaults, constraints, and normalization behavior."
|
|
1496
|
-
]),
|
|
1497
|
-
sectionSpec("## Outputs", [
|
|
1498
|
-
"Document returned values, emitted files/events, state changes, side effects, and success diagnostics.",
|
|
1499
|
-
"Make externally observable behavior explicit enough for compatibility review."
|
|
1500
|
-
]),
|
|
1501
|
-
sectionSpec("## Errors And Diagnostics", [
|
|
1502
|
-
"List error classes, exit/status codes, user-facing diagnostics, retries, and recoverability expectations.",
|
|
1503
|
-
"Distinguish validation errors, dependency failures, authorization failures, and internal faults when applicable."
|
|
1504
|
-
]),
|
|
1505
|
-
sectionSpec("## Compatibility Rules", [
|
|
1506
|
-
"State backward/forward compatibility guarantees, tolerated inputs, deprecation rules, and breaking-change triggers.",
|
|
1507
|
-
"Include compatibility tests or review questions that protect the contract."
|
|
1508
|
-
]),
|
|
1509
|
-
sectionSpec("## Versioning And Migration", [
|
|
1510
|
-
"Document version negotiation, schema/API version fields, rollout requirements, migration steps, and rollback expectations.",
|
|
1511
|
-
"State 'Not versioned' only when the implementation truly has no versioning or migration surface."
|
|
1512
|
-
])
|
|
1513
|
-
]);
|
|
1514
|
-
};
|
|
1515
|
-
var renderArchitectureDocTemplateFile = () => {
|
|
1516
|
-
return renderTypedTruthDocTemplate("engineering-architecture", "{{title}}", [
|
|
1517
|
-
sectionSpec("## System Role", [
|
|
1518
|
-
"Describe the current architectural role of this subsystem/component in the larger system.",
|
|
1519
|
-
"State the primary responsibilities, consumers, providers, and why this boundary exists now."
|
|
1520
|
-
]),
|
|
1521
|
-
sectionSpec("## Boundaries", [
|
|
1522
|
-
"Define owned code/config/data, external dependencies, trust boundaries, and interfaces crossed by this architecture.",
|
|
1523
|
-
"Name what is deliberately outside the boundary and link neighboring architecture or contract docs when they own it."
|
|
1524
|
-
]),
|
|
1525
|
-
sectionSpec("## Components", [
|
|
1526
|
-
"List the major runtime/build-time components, modules, services, jobs, or generated artifacts and their responsibilities.",
|
|
1527
|
-
"Keep the component list current and evidence-backed; avoid speculative target architecture."
|
|
1528
|
-
]),
|
|
1529
|
-
sectionSpec("## Data And Control Flow", [
|
|
1530
|
-
"Describe important data movement, command/control paths, synchronization points, state ownership, and failure paths.",
|
|
1531
|
-
"Call out persistence, queues, caches, external calls, and security-sensitive transitions where relevant."
|
|
1532
|
-
]),
|
|
1533
|
-
sectionSpec("## Ownership", [
|
|
1534
|
-
"Document team/module ownership, review responsibility, operational responsibility, and escalation paths if known.",
|
|
1535
|
-
"If ownership is inferred from codeowners, config, or repository structure, cite that evidence."
|
|
1536
|
-
]),
|
|
1537
|
-
sectionSpec("## Cross-Cutting Constraints", [
|
|
1538
|
-
"Record active constraints such as security, privacy, reliability, performance, portability, maintainability, compliance, and cost.",
|
|
1539
|
-
"Tie constraints to source evidence, tests, standards, or operational requirements where available."
|
|
1540
|
-
])
|
|
1541
|
-
]);
|
|
1542
|
-
};
|
|
1543
|
-
var renderWorkflowDocTemplateFile = () => {
|
|
1544
|
-
return renderTypedTruthDocTemplate("engineering-workflow", "{{title}}", [
|
|
1545
|
-
sectionSpec("## Triggers", [
|
|
1546
|
-
"List events, commands, schedules, user actions, webhooks, or dependency signals that start this workflow.",
|
|
1547
|
-
"Include preconditions, authorization requirements, debounce/coalescing behavior, and disabled states when applicable."
|
|
1548
|
-
]),
|
|
1549
|
-
sectionSpec("## Inputs", [
|
|
1550
|
-
"Document data, files, config, context, credentials, and environmental assumptions consumed by the workflow.",
|
|
1551
|
-
"Include validation, defaults, and normalization that happen before execution."
|
|
1552
|
-
]),
|
|
1553
|
-
sectionSpec("## Execution Model", [
|
|
1554
|
-
"Describe synchronous/asynchronous execution, concurrency, locking, leases, batching, ordering, and idempotency behavior.",
|
|
1555
|
-
"State whether the workflow waits for user action, runs in the background, is distributed, or is delegated to another system."
|
|
1556
|
-
]),
|
|
1557
|
-
sectionSpec("## Steps", [
|
|
1558
|
-
"Capture the current ordered steps or phases at a level useful for maintenance and review.",
|
|
1559
|
-
"Reference implementation entrypoints instead of duplicating line-by-line code behavior."
|
|
1560
|
-
]),
|
|
1561
|
-
sectionSpec("## State, Retry, And Failure Behavior", [
|
|
1562
|
-
"Document state transitions, retries, timeouts, compensation, fallback, partial-success, and terminal-failure behavior.",
|
|
1563
|
-
"Make externally visible failure semantics and recovery responsibilities clear."
|
|
1564
|
-
]),
|
|
1565
|
-
sectionSpec("## Outputs", [
|
|
1566
|
-
"List artifacts, state changes, notifications, logs, metrics, diagnostics, and downstream triggers produced by the workflow.",
|
|
1567
|
-
"Include success criteria and handoff points to other truth docs or systems."
|
|
1568
|
-
])
|
|
1569
|
-
]);
|
|
1570
|
-
};
|
|
1571
|
-
var renderOperationsDocTemplateFile = () => {
|
|
1572
|
-
return renderTypedTruthDocTemplate("engineering-operations", "{{title}}", [
|
|
1573
|
-
sectionSpec("## Operational Surface", [
|
|
1574
|
-
"Describe what operators, maintainers, or automated systems can observe or control for this surface.",
|
|
1575
|
-
"Include commands, dashboards, alerts, runbooks, jobs, or operational APIs that define current operations."
|
|
1576
|
-
]),
|
|
1577
|
-
sectionSpec("## Runtime Topology", [
|
|
1578
|
-
"Document services, processes, containers, hosts, regions, dependencies, queues, stores, and network boundaries involved at runtime.",
|
|
1579
|
-
"State single-node/local behavior explicitly when there is no distributed topology."
|
|
1580
|
-
]),
|
|
1581
|
-
sectionSpec("## Configuration", [
|
|
1582
|
-
"List operational config, environment variables, feature flags, secrets references, defaults, and reload/restart requirements.",
|
|
1583
|
-
"Do not include secret values; describe storage and rotation expectations instead."
|
|
1584
|
-
]),
|
|
1585
|
-
sectionSpec("## Permissions", [
|
|
1586
|
-
"Document required identities, roles, scopes, filesystem/network permissions, and least-privilege boundaries.",
|
|
1587
|
-
"Include user-facing authorization behavior and operator access requirements when relevant."
|
|
1588
|
-
]),
|
|
1589
|
-
sectionSpec("## Deployment And Rollback", [
|
|
1590
|
-
"Describe deployment mechanism, migration ordering, compatibility windows, rollback path, and known irreversible operations.",
|
|
1591
|
-
"Call out manual review points, smoke checks, and post-deploy verification responsibilities."
|
|
1592
|
-
]),
|
|
1593
|
-
sectionSpec("## Availability And Observability", [
|
|
1594
|
-
"Capture availability expectations, health checks, metrics, logs, traces, alerts, SLO/error-budget signals, and known blind spots.",
|
|
1595
|
-
"Include what maintainers should inspect first during incidents or degraded behavior."
|
|
1596
|
-
])
|
|
1597
|
-
]);
|
|
1598
|
-
};
|
|
1599
|
-
var renderTestBehaviorDocTemplateFile = () => {
|
|
1600
|
-
return renderTypedTruthDocTemplate("engineering-test-behavior", "{{title}}", [
|
|
1601
|
-
sectionSpec("## Test Surface", [
|
|
1602
|
-
"Define the behavior, contract, architecture, or workflow surface these tests verify.",
|
|
1603
|
-
"Link the canonical truth docs and code paths the tests are meant to protect."
|
|
1604
|
-
]),
|
|
1605
|
-
sectionSpec("## Fixtures And Data Model", [
|
|
1606
|
-
"Document fixtures, factories, seeds, mocks/fakes, test repositories, external-service substitutes, and data lifecycle rules.",
|
|
1607
|
-
"Include cleanup, determinism, privacy, and cross-test contamination constraints."
|
|
1608
|
-
]),
|
|
1609
|
-
sectionSpec("## Execution Model", [
|
|
1610
|
-
"Describe how tests run: command, framework, parallelism, isolation, network/filesystem assumptions, and required services.",
|
|
1611
|
-
"State whether tests are unit, integration, e2e, contract, smoke, regression, or generated checks."
|
|
1612
|
-
]),
|
|
1613
|
-
sectionSpec("## Assertions And Invariants", [
|
|
1614
|
-
"List the critical assertions, invariants, failure modes, and negative cases that make the tests meaningful.",
|
|
1615
|
-
"Tie assertions to product/contract rules rather than incidental implementation details."
|
|
1616
|
-
]),
|
|
1617
|
-
sectionSpec("## Isolation Rules", [
|
|
1618
|
-
"Document transaction boundaries, temp directories, fake clocks, network blocking, shared resources, and teardown rules.",
|
|
1619
|
-
"Call out known order dependencies or flake risks and how they are controlled."
|
|
1620
|
-
]),
|
|
1621
|
-
sectionSpec("## Reporting And Failure Semantics", [
|
|
1622
|
-
"Describe diagnostics, snapshots, logs, coverage signals, retry policy, and how maintainers should interpret failures.",
|
|
1623
|
-
"Include escalation or quarantine criteria for flaky or environment-sensitive tests."
|
|
1624
|
-
])
|
|
1625
|
-
]);
|
|
1626
|
-
};
|
|
1627
|
-
|
|
1628
|
-
// src/config/command.ts
|
|
1629
|
-
var CONFIG_PATH = ".truthmark/config.yml";
|
|
1630
|
-
var configExists = async (rootDir) => {
|
|
1631
|
-
try {
|
|
1632
|
-
await fs3.stat(resolveRepoPath(rootDir, CONFIG_PATH));
|
|
1633
|
-
return true;
|
|
1634
|
-
} catch (error) {
|
|
1635
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
1636
|
-
return false;
|
|
1637
|
-
}
|
|
1638
|
-
throw error;
|
|
1639
|
-
}
|
|
1065
|
+
const commonDirOutput = (await runGit(cwd, ["rev-parse", "--git-common-dir"])).stdout.trim();
|
|
1066
|
+
const commonDir = await realpathOrResolved(path3.resolve(worktreePath, commonDirOutput));
|
|
1067
|
+
const repositoryRoot = path3.basename(commonDir) === ".git" ? path3.dirname(commonDir) : worktreePath;
|
|
1068
|
+
const branchResult = await runGit(cwd, ["symbolic-ref", "--quiet", "--short", "HEAD"], false);
|
|
1069
|
+
const headResult = await runGit(cwd, ["rev-parse", "--verify", "HEAD"], false);
|
|
1070
|
+
const branchName = branchResult.exitCode === 0 ? branchResult.stdout.trim() : null;
|
|
1071
|
+
const headSha = headResult.exitCode === 0 ? headResult.stdout.trim() : null;
|
|
1072
|
+
const isDetached = branchName === null;
|
|
1073
|
+
const isUnborn = !isDetached && headSha === null;
|
|
1074
|
+
return {
|
|
1075
|
+
repositoryRoot,
|
|
1076
|
+
worktreePath,
|
|
1077
|
+
branchName,
|
|
1078
|
+
headSha,
|
|
1079
|
+
isDetached,
|
|
1080
|
+
isUnborn
|
|
1081
|
+
};
|
|
1640
1082
|
};
|
|
1641
|
-
var
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
isUnborn: repository.isUnborn,
|
|
1655
|
-
path: CONFIG_PATH,
|
|
1656
|
-
content
|
|
1657
|
-
}
|
|
1658
|
-
};
|
|
1659
|
-
}
|
|
1660
|
-
const exists = await configExists(repository.worktreePath);
|
|
1661
|
-
if (exists && !options.force) {
|
|
1662
|
-
return {
|
|
1663
|
-
command: "config",
|
|
1664
|
-
summary: "Truthmark config already exists. Use --force to overwrite it.",
|
|
1665
|
-
diagnostics: [
|
|
1666
|
-
{
|
|
1667
|
-
category: "config",
|
|
1668
|
-
severity: "review",
|
|
1669
|
-
message: "Existing .truthmark/config.yml was left unchanged.",
|
|
1670
|
-
file: CONFIG_PATH
|
|
1083
|
+
var resolveWorktreePath = (repository, relativePath) => {
|
|
1084
|
+
const resolvedPath = path3.resolve(repository.worktreePath, relativePath);
|
|
1085
|
+
let currentPath = resolvedPath;
|
|
1086
|
+
const missingSegments = [];
|
|
1087
|
+
const resolveContainedPath = () => {
|
|
1088
|
+
while (true) {
|
|
1089
|
+
try {
|
|
1090
|
+
return missingSegments.reduceRight((resolvedExistingPath, segment) => {
|
|
1091
|
+
return path3.join(resolvedExistingPath, segment);
|
|
1092
|
+
}, realpathSync(currentPath));
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
1095
|
+
throw error;
|
|
1671
1096
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
isUnborn: repository.isUnborn
|
|
1679
|
-
}
|
|
1680
|
-
};
|
|
1681
|
-
}
|
|
1682
|
-
const result = options.force ? await writeRepoFile(repository.worktreePath, CONFIG_PATH, content) : await ensureRepoFile(repository.worktreePath, CONFIG_PATH, content);
|
|
1683
|
-
return {
|
|
1684
|
-
command: "config",
|
|
1685
|
-
summary: `Wrote Truthmark config to ${CONFIG_PATH}. Review it before running truthmark init.`,
|
|
1686
|
-
diagnostics: [
|
|
1687
|
-
{
|
|
1688
|
-
category: "config",
|
|
1689
|
-
severity: "action",
|
|
1690
|
-
message: result.status === "updated" ? `Updated ${CONFIG_PATH}.` : `Created ${CONFIG_PATH}.`,
|
|
1691
|
-
file: CONFIG_PATH
|
|
1097
|
+
const parentPath = path3.dirname(currentPath);
|
|
1098
|
+
if (parentPath === currentPath) {
|
|
1099
|
+
return resolvedPath;
|
|
1100
|
+
}
|
|
1101
|
+
missingSegments.unshift(path3.basename(currentPath));
|
|
1102
|
+
currentPath = parentPath;
|
|
1692
1103
|
}
|
|
1693
|
-
],
|
|
1694
|
-
data: {
|
|
1695
|
-
repositoryRoot: repository.repositoryRoot,
|
|
1696
|
-
worktreePath: repository.worktreePath,
|
|
1697
|
-
branchName: repository.branchName,
|
|
1698
|
-
isDetached: repository.isDetached,
|
|
1699
|
-
isUnborn: repository.isUnborn
|
|
1700
1104
|
}
|
|
1701
1105
|
};
|
|
1106
|
+
const containedPath = resolveContainedPath();
|
|
1107
|
+
if (containedPath !== repository.worktreePath && !containedPath.startsWith(`${repository.worktreePath}${path3.sep}`)) {
|
|
1108
|
+
throw new Error("resolved path must stay inside the active worktree");
|
|
1109
|
+
}
|
|
1110
|
+
return resolvedPath;
|
|
1702
1111
|
};
|
|
1703
1112
|
|
|
1704
|
-
// src/init/
|
|
1705
|
-
import fs7 from "fs/promises";
|
|
1706
|
-
|
|
1707
|
-
// src/config/load.ts
|
|
1113
|
+
// src/init/hierarchy.ts
|
|
1708
1114
|
import fs4 from "fs/promises";
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
import { parse as
|
|
1712
|
-
var
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1115
|
+
|
|
1116
|
+
// src/routing/areas.ts
|
|
1117
|
+
import { parse as parse3 } from "yaml";
|
|
1118
|
+
var TRUTH_DOCUMENT_KINDS = [
|
|
1119
|
+
"product-capability",
|
|
1120
|
+
"engineering-behavior",
|
|
1121
|
+
"engineering-contract",
|
|
1122
|
+
"engineering-workflow",
|
|
1123
|
+
"engineering-architecture",
|
|
1124
|
+
"engineering-operations",
|
|
1125
|
+
"engineering-test-behavior"
|
|
1126
|
+
];
|
|
1127
|
+
var uniqueSorted = (values) => [...new Set(values)].sort();
|
|
1128
|
+
var mergeTruthDocumentEntryRelationships = (first, second) => ({
|
|
1129
|
+
...first,
|
|
1130
|
+
realizedBy: uniqueSorted([...first.realizedBy, ...second.realizedBy]),
|
|
1131
|
+
realizes: uniqueSorted([...first.realizes, ...second.realizes]),
|
|
1132
|
+
dependsOn: uniqueSorted([...first.dependsOn, ...second.dependsOn])
|
|
1719
1133
|
});
|
|
1720
|
-
var
|
|
1721
|
-
|
|
1722
|
-
|
|
1134
|
+
var DEFAULT_PRODUCT_TRUTH_DOCS_ROOT = "docs/truthmark/product";
|
|
1135
|
+
var DEFAULT_ENGINEERING_TRUTH_DOCS_ROOT = "docs/truthmark/engineering";
|
|
1136
|
+
var slugify = (value) => {
|
|
1137
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1723
1138
|
};
|
|
1724
|
-
var
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1139
|
+
var createAreaDiagnostic = (message, area, severity = "error") => {
|
|
1140
|
+
return {
|
|
1141
|
+
category: "area-index",
|
|
1142
|
+
severity,
|
|
1143
|
+
message,
|
|
1144
|
+
area
|
|
1145
|
+
};
|
|
1729
1146
|
};
|
|
1730
|
-
var
|
|
1731
|
-
return
|
|
1147
|
+
var parseListSection = (sectionLines) => {
|
|
1148
|
+
return sectionLines.map((line) => line.trim()).filter((line) => line.startsWith("- ")).map((line) => line.slice(2).trim().replaceAll("\\*", "*")).filter((line) => line.length > 0);
|
|
1732
1149
|
};
|
|
1733
|
-
var
|
|
1734
|
-
|
|
1735
|
-
const normalizedLeft = normalizeRepoRelativePath(left);
|
|
1736
|
-
const normalizedRight = normalizeRepoRelativePath(right);
|
|
1737
|
-
return normalizedLeft === normalizedRight || normalizedLeft.startsWith(`${normalizedRight}/`) || normalizedRight.startsWith(`${normalizedLeft}/`);
|
|
1150
|
+
var isTruthDocumentKind = (value) => {
|
|
1151
|
+
return typeof value === "string" && TRUTH_DOCUMENT_KINDS.includes(value);
|
|
1738
1152
|
};
|
|
1739
|
-
var
|
|
1740
|
-
|
|
1741
|
-
".
|
|
1742
|
-
".
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1153
|
+
var inferTruthDocumentKindFromPath = (documentPath, options = {}) => {
|
|
1154
|
+
const normalizedPath = documentPath.replaceAll("\\", "/");
|
|
1155
|
+
const productTruthRoot = (options.productTruthRoot ?? DEFAULT_PRODUCT_TRUTH_DOCS_ROOT)?.replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
1156
|
+
const engineeringTruthRoot = (options.engineeringTruthRoot ?? options.truthDocsRoot ?? DEFAULT_ENGINEERING_TRUTH_DOCS_ROOT)?.replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
1157
|
+
if (productTruthRoot && normalizedPath.startsWith(`${productTruthRoot}/`)) {
|
|
1158
|
+
return "product-capability";
|
|
1159
|
+
}
|
|
1160
|
+
if (engineeringTruthRoot && normalizedPath.startsWith(`${engineeringTruthRoot}/`)) {
|
|
1161
|
+
if (normalizedPath.includes("/contracts/")) return "engineering-contract";
|
|
1162
|
+
if (normalizedPath.includes("/workflows/")) return "engineering-workflow";
|
|
1163
|
+
if (normalizedPath.includes("/architecture/"))
|
|
1164
|
+
return "engineering-architecture";
|
|
1165
|
+
if (normalizedPath.includes("/operations/"))
|
|
1166
|
+
return "engineering-operations";
|
|
1167
|
+
if (normalizedPath.includes("/tests/")) return "engineering-test-behavior";
|
|
1168
|
+
return "engineering-behavior";
|
|
1169
|
+
}
|
|
1170
|
+
return null;
|
|
1171
|
+
};
|
|
1172
|
+
var inferTruthDocumentLaneFromPath = (documentPath, options = {}) => {
|
|
1173
|
+
const normalizedPath = documentPath.replaceAll("\\", "/");
|
|
1174
|
+
const productTruthRoot = (options.productTruthRoot ?? DEFAULT_PRODUCT_TRUTH_DOCS_ROOT).replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
1175
|
+
const engineeringTruthRoot = (options.engineeringTruthRoot ?? options.truthDocsRoot ?? DEFAULT_ENGINEERING_TRUTH_DOCS_ROOT).replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
1176
|
+
if (normalizedPath.startsWith(`${productTruthRoot}/`)) {
|
|
1177
|
+
return "product";
|
|
1178
|
+
}
|
|
1179
|
+
if (normalizedPath.startsWith(`${engineeringTruthRoot}/`)) {
|
|
1180
|
+
return "engineering";
|
|
1181
|
+
}
|
|
1182
|
+
return null;
|
|
1183
|
+
};
|
|
1184
|
+
var laneForTruthDocumentKind = (kind) => {
|
|
1185
|
+
return kind.startsWith("product-") ? "product" : "engineering";
|
|
1186
|
+
};
|
|
1187
|
+
var docTypeForTruthDocumentKind = (kind) => {
|
|
1188
|
+
if (kind.startsWith("product-")) {
|
|
1189
|
+
return "product";
|
|
1190
|
+
}
|
|
1191
|
+
return kind.slice("engineering-".length);
|
|
1192
|
+
};
|
|
1193
|
+
var parseStringListField = (rawEntry, field) => {
|
|
1194
|
+
if (!rawEntry || typeof rawEntry !== "object" || !(field in rawEntry)) {
|
|
1752
1195
|
return [];
|
|
1753
1196
|
}
|
|
1754
|
-
const
|
|
1197
|
+
const value = rawEntry[field];
|
|
1198
|
+
if (!Array.isArray(value)) {
|
|
1199
|
+
return [];
|
|
1200
|
+
}
|
|
1201
|
+
return value.filter((entry) => typeof entry === "string");
|
|
1202
|
+
};
|
|
1203
|
+
var findTruthDocumentsYamlFenceRange = (sectionLines) => {
|
|
1204
|
+
const trimmedLines = sectionLines.map((line) => line.trim());
|
|
1205
|
+
const openingFenceIndex = trimmedLines.findIndex(
|
|
1206
|
+
(line) => /^```(?:yaml|yml)?$/u.test(line)
|
|
1207
|
+
);
|
|
1208
|
+
if (openingFenceIndex === -1) {
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
const closingFenceIndex = trimmedLines.findIndex(
|
|
1212
|
+
(line, index) => index > openingFenceIndex && line === "```"
|
|
1213
|
+
);
|
|
1214
|
+
return {
|
|
1215
|
+
openingFenceIndex,
|
|
1216
|
+
closingFenceIndex: closingFenceIndex === -1 ? null : closingFenceIndex
|
|
1217
|
+
};
|
|
1218
|
+
};
|
|
1219
|
+
var parseTruthDocumentsFromList = (sectionLines, areaName, options) => {
|
|
1220
|
+
const diagnostics = [];
|
|
1221
|
+
const truthDocuments = parseListSection(sectionLines);
|
|
1222
|
+
const truthDocumentEntries = truthDocuments.map((documentPath) => {
|
|
1223
|
+
const inferredKind = inferTruthDocumentKindFromPath(documentPath, options);
|
|
1224
|
+
const inferredLane = inferTruthDocumentLaneFromPath(documentPath, options);
|
|
1225
|
+
if (!inferredKind) {
|
|
1226
|
+
diagnostics.push(
|
|
1227
|
+
createAreaDiagnostic(
|
|
1228
|
+
`Truth document ${documentPath} does not match a known kind path convention; defaulting to behavior.`,
|
|
1229
|
+
areaName,
|
|
1230
|
+
"review"
|
|
1231
|
+
)
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
return {
|
|
1235
|
+
path: documentPath,
|
|
1236
|
+
kind: inferredKind ?? "engineering-behavior",
|
|
1237
|
+
kindSource: inferredKind ? "inferred" : "defaulted",
|
|
1238
|
+
lane: inferredLane ?? "engineering",
|
|
1239
|
+
laneSource: inferredLane ? "inferred" : "defaulted",
|
|
1240
|
+
realizedBy: [],
|
|
1241
|
+
realizes: [],
|
|
1242
|
+
dependsOn: []
|
|
1243
|
+
};
|
|
1244
|
+
});
|
|
1245
|
+
return {
|
|
1246
|
+
truthDocuments,
|
|
1247
|
+
truthDocumentEntries,
|
|
1248
|
+
diagnostics
|
|
1249
|
+
};
|
|
1250
|
+
};
|
|
1251
|
+
var parseTruthDocumentsFromYaml = (sectionLines, areaName, options) => {
|
|
1252
|
+
const yamlFenceRange = findTruthDocumentsYamlFenceRange(sectionLines);
|
|
1253
|
+
if (!yamlFenceRange) {
|
|
1254
|
+
return {
|
|
1255
|
+
truthDocuments: [],
|
|
1256
|
+
truthDocumentEntries: [],
|
|
1257
|
+
diagnostics: []
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
if (yamlFenceRange.closingFenceIndex === null) {
|
|
1261
|
+
return {
|
|
1262
|
+
truthDocuments: [],
|
|
1263
|
+
truthDocumentEntries: [],
|
|
1264
|
+
diagnostics: [
|
|
1265
|
+
createAreaDiagnostic(
|
|
1266
|
+
`Area ${areaName} has an unterminated fenced YAML Truth documents block.`,
|
|
1267
|
+
areaName
|
|
1268
|
+
)
|
|
1269
|
+
]
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
let parsedBlock;
|
|
1273
|
+
try {
|
|
1274
|
+
parsedBlock = parse3(
|
|
1275
|
+
sectionLines.slice(
|
|
1276
|
+
yamlFenceRange.openingFenceIndex + 1,
|
|
1277
|
+
yamlFenceRange.closingFenceIndex
|
|
1278
|
+
).join("\n")
|
|
1279
|
+
);
|
|
1280
|
+
} catch (error) {
|
|
1281
|
+
return {
|
|
1282
|
+
truthDocuments: [],
|
|
1283
|
+
truthDocumentEntries: [],
|
|
1284
|
+
diagnostics: [
|
|
1285
|
+
createAreaDiagnostic(
|
|
1286
|
+
`Area ${areaName} has invalid YAML truth document metadata: ${error instanceof Error ? error.message : String(error)}.`,
|
|
1287
|
+
areaName
|
|
1288
|
+
)
|
|
1289
|
+
]
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
const rawEntries = parsedBlock && typeof parsedBlock === "object" && "truth_documents" in parsedBlock ? parsedBlock.truth_documents : null;
|
|
1293
|
+
if (!Array.isArray(rawEntries)) {
|
|
1294
|
+
return {
|
|
1295
|
+
truthDocuments: [],
|
|
1296
|
+
truthDocumentEntries: [],
|
|
1297
|
+
diagnostics: [
|
|
1298
|
+
createAreaDiagnostic(
|
|
1299
|
+
`Area ${areaName} must define a truth_documents array inside the fenced YAML block.`,
|
|
1300
|
+
areaName
|
|
1301
|
+
)
|
|
1302
|
+
]
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1755
1305
|
const diagnostics = [];
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
);
|
|
1306
|
+
const truthDocumentEntries = [];
|
|
1307
|
+
for (const rawEntry of rawEntries) {
|
|
1308
|
+
const path13 = rawEntry && typeof rawEntry === "object" && "path" in rawEntry ? rawEntry.path : null;
|
|
1309
|
+
const kind = rawEntry && typeof rawEntry === "object" && "kind" in rawEntry ? rawEntry.kind : null;
|
|
1310
|
+
const lane = rawEntry && typeof rawEntry === "object" && "lane" in rawEntry ? rawEntry.lane : null;
|
|
1311
|
+
const inferredKind = typeof path13 === "string" ? inferTruthDocumentKindFromPath(path13, options) : null;
|
|
1312
|
+
const inferredLane = typeof path13 === "string" ? inferTruthDocumentLaneFromPath(path13, options) : null;
|
|
1313
|
+
const normalizedKind = isTruthDocumentKind(kind) ? kind : inferredKind;
|
|
1314
|
+
const normalizedLane = lane === "product" || lane === "engineering" ? lane : normalizedKind ? laneForTruthDocumentKind(normalizedKind) : inferredLane;
|
|
1315
|
+
if (typeof path13 !== "string" || path13.trim().length === 0 || !normalizedKind || !normalizedLane) {
|
|
1316
|
+
diagnostics.push(
|
|
1317
|
+
createAreaDiagnostic(
|
|
1318
|
+
`Area ${areaName} truth_documents entries must include non-empty path plus valid lane and kind fields.`,
|
|
1319
|
+
areaName
|
|
1320
|
+
)
|
|
1321
|
+
);
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
truthDocumentEntries.push({
|
|
1325
|
+
path: path13.trim(),
|
|
1326
|
+
kind: normalizedKind,
|
|
1327
|
+
kindSource: isTruthDocumentKind(kind) ? "explicit" : "inferred",
|
|
1328
|
+
lane: normalizedLane,
|
|
1329
|
+
laneSource: lane === "product" || lane === "engineering" ? "explicit" : "inferred",
|
|
1330
|
+
realizedBy: parseStringListField(rawEntry, "realized_by"),
|
|
1331
|
+
realizes: parseStringListField(rawEntry, "realizes"),
|
|
1332
|
+
dependsOn: parseStringListField(rawEntry, "depends_on")
|
|
1333
|
+
});
|
|
1763
1334
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1335
|
+
return {
|
|
1336
|
+
truthDocuments: truthDocumentEntries.map((entry) => entry.path),
|
|
1337
|
+
truthDocumentEntries,
|
|
1338
|
+
diagnostics
|
|
1339
|
+
};
|
|
1340
|
+
};
|
|
1341
|
+
var parseTruthDocumentsSection = (sectionLines, areaName, options) => {
|
|
1342
|
+
const yamlFenceRange = findTruthDocumentsYamlFenceRange(sectionLines);
|
|
1343
|
+
if (!yamlFenceRange) {
|
|
1344
|
+
return parseTruthDocumentsFromList(sectionLines, areaName, options);
|
|
1771
1345
|
}
|
|
1772
|
-
|
|
1346
|
+
const yamlResult = parseTruthDocumentsFromYaml(
|
|
1347
|
+
sectionLines,
|
|
1348
|
+
areaName,
|
|
1349
|
+
options
|
|
1350
|
+
);
|
|
1351
|
+
if (yamlResult.diagnostics.length > 0 || yamlFenceRange.closingFenceIndex === null) {
|
|
1352
|
+
return yamlResult;
|
|
1353
|
+
}
|
|
1354
|
+
return yamlResult;
|
|
1773
1355
|
};
|
|
1774
|
-
var
|
|
1356
|
+
var parseAreasMarkdown = (source, options = {}) => {
|
|
1357
|
+
const lines = source.split("\n");
|
|
1775
1358
|
const diagnostics = [];
|
|
1776
|
-
const
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1359
|
+
const areas = [];
|
|
1360
|
+
const truthDocumentReferences = [];
|
|
1361
|
+
const areaFileReferences = [];
|
|
1362
|
+
let areaIndex = 0;
|
|
1363
|
+
let currentAreaName = null;
|
|
1364
|
+
let currentSections = /* @__PURE__ */ new Map();
|
|
1365
|
+
let currentSectionName = null;
|
|
1366
|
+
const flushArea = () => {
|
|
1367
|
+
if (!currentAreaName) {
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
const truthDocumentResult = parseTruthDocumentsSection(
|
|
1371
|
+
currentSections.get("Truth documents") ?? [],
|
|
1372
|
+
currentAreaName,
|
|
1373
|
+
options
|
|
1374
|
+
);
|
|
1375
|
+
const { truthDocuments, truthDocumentEntries } = truthDocumentResult;
|
|
1376
|
+
const areaFiles = parseListSection(currentSections.get("Area files") ?? []);
|
|
1377
|
+
const codeSurface = parseListSection(
|
|
1378
|
+
currentSections.get("Code surface") ?? []
|
|
1379
|
+
);
|
|
1380
|
+
const updateTruthWhen = parseListSection(
|
|
1381
|
+
currentSections.get("Update truth when") ?? []
|
|
1785
1382
|
);
|
|
1383
|
+
const areaKey = slugify(currentAreaName);
|
|
1384
|
+
const areaId = areaKey.length > 0 ? areaKey : `area-${areaIndex}`;
|
|
1385
|
+
const hasTruthDocuments = truthDocuments.length > 0;
|
|
1386
|
+
const hasAreaFiles = areaFiles.length > 0;
|
|
1387
|
+
areaIndex += 1;
|
|
1388
|
+
diagnostics.push(...truthDocumentResult.diagnostics);
|
|
1389
|
+
if (hasTruthDocuments) {
|
|
1390
|
+
truthDocumentReferences.push({
|
|
1391
|
+
id: areaId,
|
|
1392
|
+
name: currentAreaName,
|
|
1393
|
+
key: areaKey,
|
|
1394
|
+
truthDocuments,
|
|
1395
|
+
truthDocumentEntries
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
if (hasTruthDocuments === hasAreaFiles || codeSurface.length === 0 || updateTruthWhen.length === 0) {
|
|
1399
|
+
diagnostics.push(
|
|
1400
|
+
createAreaDiagnostic(
|
|
1401
|
+
`Area ${currentAreaName} must define exactly one of Truth documents or Area files, plus Code surface and Update truth when sections.`,
|
|
1402
|
+
currentAreaName
|
|
1403
|
+
)
|
|
1404
|
+
);
|
|
1405
|
+
} else if (hasAreaFiles) {
|
|
1406
|
+
areaFileReferences.push({
|
|
1407
|
+
id: areaId,
|
|
1408
|
+
name: currentAreaName,
|
|
1409
|
+
key: areaKey,
|
|
1410
|
+
areaFiles,
|
|
1411
|
+
codeSurface,
|
|
1412
|
+
updateTruthWhen
|
|
1413
|
+
});
|
|
1414
|
+
} else {
|
|
1415
|
+
areas.push({
|
|
1416
|
+
id: areaId,
|
|
1417
|
+
name: currentAreaName,
|
|
1418
|
+
key: areaKey,
|
|
1419
|
+
truthDocuments,
|
|
1420
|
+
truthDocumentEntries,
|
|
1421
|
+
codeSurface,
|
|
1422
|
+
updateTruthWhen
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
currentAreaName = null;
|
|
1426
|
+
currentSections = /* @__PURE__ */ new Map();
|
|
1427
|
+
currentSectionName = null;
|
|
1428
|
+
};
|
|
1429
|
+
for (const line of lines) {
|
|
1430
|
+
const areaHeadingMatch = line.match(/^\s{0,3}##\s+(.*)$/u);
|
|
1431
|
+
if (areaHeadingMatch) {
|
|
1432
|
+
const heading = areaHeadingMatch[1]?.trim() ?? null;
|
|
1433
|
+
flushArea();
|
|
1434
|
+
currentAreaName = heading === "Source References" ? null : heading;
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
if (!currentAreaName) {
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
if (/^(Truth documents|Area files|Code surface|Update truth when):$/u.test(
|
|
1441
|
+
line.trim()
|
|
1442
|
+
)) {
|
|
1443
|
+
currentSectionName = line.trim().slice(0, -1);
|
|
1444
|
+
currentSections.set(currentSectionName, []);
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
if (currentSectionName) {
|
|
1448
|
+
currentSections.get(currentSectionName)?.push(line);
|
|
1449
|
+
}
|
|
1786
1450
|
}
|
|
1787
|
-
|
|
1788
|
-
};
|
|
1789
|
-
var normalizeConfig = (rawConfig) => {
|
|
1790
|
-
const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
|
|
1791
|
-
const routesIndex = joinWorkspacePath(
|
|
1792
|
-
workspace,
|
|
1793
|
-
DERIVED_TRUTHMARK_PATHS.routesIndex
|
|
1794
|
-
);
|
|
1795
|
-
const routeAreasRoot = joinWorkspacePath(
|
|
1796
|
-
workspace,
|
|
1797
|
-
DERIVED_TRUTHMARK_PATHS.routeAreasRoot
|
|
1798
|
-
);
|
|
1799
|
-
const productTruthRoot = joinWorkspacePath(
|
|
1800
|
-
workspace,
|
|
1801
|
-
DERIVED_TRUTHMARK_PATHS.productTruthRoot
|
|
1802
|
-
);
|
|
1803
|
-
const engineeringTruthRoot = joinWorkspacePath(
|
|
1804
|
-
workspace,
|
|
1805
|
-
DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
|
|
1806
|
-
);
|
|
1807
|
-
const templatesRoot = joinWorkspacePath(
|
|
1808
|
-
workspace,
|
|
1809
|
-
DERIVED_TRUTHMARK_PATHS.templatesRoot
|
|
1810
|
-
);
|
|
1811
|
-
const portalOutput = portalOutputFor(workspace);
|
|
1812
|
-
const portalTemplate = joinWorkspacePath(
|
|
1813
|
-
workspace,
|
|
1814
|
-
DERIVED_TRUTHMARK_PATHS.portalTemplate
|
|
1815
|
-
);
|
|
1451
|
+
flushArea();
|
|
1816
1452
|
return {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
routes: {
|
|
1822
|
-
index: DERIVED_TRUTHMARK_PATHS.routesIndex,
|
|
1823
|
-
areas: DERIVED_TRUTHMARK_PATHS.routeAreasRoot,
|
|
1824
|
-
defaultArea: DERIVED_TRUTHMARK_PATHS.defaultArea,
|
|
1825
|
-
maxDelegationDepth: DERIVED_TRUTHMARK_PATHS.maxDelegationDepth
|
|
1826
|
-
},
|
|
1827
|
-
truth: {
|
|
1828
|
-
productRoot: DERIVED_TRUTHMARK_PATHS.productTruthRoot,
|
|
1829
|
-
engineeringRoot: DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
|
|
1830
|
-
},
|
|
1831
|
-
templates: {
|
|
1832
|
-
root: DERIVED_TRUTHMARK_PATHS.templatesRoot
|
|
1833
|
-
},
|
|
1834
|
-
generated: {
|
|
1835
|
-
portal: {
|
|
1836
|
-
enabled: rawConfig.truthmark.generated.portal.enabled
|
|
1837
|
-
}
|
|
1838
|
-
},
|
|
1839
|
-
paths: {
|
|
1840
|
-
routesIndex,
|
|
1841
|
-
routeAreasRoot,
|
|
1842
|
-
productTruthRoot,
|
|
1843
|
-
engineeringTruthRoot,
|
|
1844
|
-
templatesRoot,
|
|
1845
|
-
portalOutput,
|
|
1846
|
-
portalTemplate
|
|
1847
|
-
},
|
|
1848
|
-
controlledPaths: [
|
|
1849
|
-
routesIndex,
|
|
1850
|
-
`${routeAreasRoot}/**/*.md`,
|
|
1851
|
-
`${productTruthRoot}/**/*.md`,
|
|
1852
|
-
`${engineeringTruthRoot}/**/*.md`,
|
|
1853
|
-
`${templatesRoot}/*.md`
|
|
1854
|
-
]
|
|
1855
|
-
},
|
|
1856
|
-
frontmatter: {
|
|
1857
|
-
required: rawConfig.frontmatter?.required ?? [],
|
|
1858
|
-
recommended: rawConfig.frontmatter?.recommended ?? []
|
|
1859
|
-
},
|
|
1860
|
-
ignore: rawConfig.ignore ?? []
|
|
1453
|
+
areas,
|
|
1454
|
+
truthDocumentReferences,
|
|
1455
|
+
areaFileReferences,
|
|
1456
|
+
diagnostics
|
|
1861
1457
|
};
|
|
1862
1458
|
};
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1459
|
+
|
|
1460
|
+
// src/templates/init-files.ts
|
|
1461
|
+
import path4 from "path";
|
|
1462
|
+
import { stringify as stringify2 } from "yaml";
|
|
1463
|
+
var asRelativePath = (value) => {
|
|
1464
|
+
return value.split(path4.sep).join("/");
|
|
1465
|
+
};
|
|
1466
|
+
var currentDate = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1467
|
+
var resolveRelativePath = (fromPath, toPath) => {
|
|
1468
|
+
return asRelativePath(path4.relative(path4.dirname(fromPath), toPath));
|
|
1469
|
+
};
|
|
1470
|
+
var truthRoot = resolveEngineeringTruthRoot;
|
|
1471
|
+
var renderLaneRootReadmeSummary = (lane) => {
|
|
1472
|
+
if (lane === "product") {
|
|
1473
|
+
return [
|
|
1474
|
+
"Product truth owns capability promises, boundaries, decisions, and acceptance criteria.",
|
|
1475
|
+
"Product lane docs state what must be true, why it matters, and what success means."
|
|
1476
|
+
].join(" ");
|
|
1866
1477
|
}
|
|
1867
1478
|
return [
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
message: "instruction_targets is accepted for compatibility but ignored; select platforms to control managed instruction-file writes.",
|
|
1872
|
-
file: configPath
|
|
1873
|
-
}
|
|
1874
|
-
];
|
|
1479
|
+
"Engineering truth owns current realization, contracts, architecture, workflows, operations, and tests.",
|
|
1480
|
+
"Engineering lane docs describe how the repository currently implements and operates the behavior."
|
|
1481
|
+
].join(" ");
|
|
1875
1482
|
};
|
|
1876
|
-
var
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
try {
|
|
1880
|
-
source = await fs4.readFile(absolutePath, "utf8");
|
|
1881
|
-
} catch (error) {
|
|
1882
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
1883
|
-
return {
|
|
1884
|
-
status: "missing",
|
|
1885
|
-
config: null,
|
|
1886
|
-
diagnostics: [
|
|
1887
|
-
toConfigDiagnostic("Missing .truthmark/config.yml.", CONFIG_PATH2)
|
|
1888
|
-
],
|
|
1889
|
-
configPath: CONFIG_PATH2
|
|
1890
|
-
};
|
|
1891
|
-
}
|
|
1892
|
-
throw error;
|
|
1893
|
-
}
|
|
1894
|
-
let parsedConfig;
|
|
1895
|
-
try {
|
|
1896
|
-
parsedConfig = parse2(source);
|
|
1897
|
-
} catch (error) {
|
|
1898
|
-
return {
|
|
1899
|
-
status: "invalid",
|
|
1900
|
-
config: null,
|
|
1901
|
-
diagnostics: [
|
|
1902
|
-
toConfigDiagnostic(
|
|
1903
|
-
`Invalid YAML: ${error instanceof Error ? error.message : String(error)}`,
|
|
1904
|
-
CONFIG_PATH2
|
|
1905
|
-
)
|
|
1906
|
-
],
|
|
1907
|
-
configPath: CONFIG_PATH2
|
|
1908
|
-
};
|
|
1483
|
+
var renderLaneRootLeafDocGuidance = (lane) => {
|
|
1484
|
+
if (lane === "product") {
|
|
1485
|
+
return "README.md files are indexes, not Truth Sync targets. Keep product truth in bounded capability docs.";
|
|
1909
1486
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1487
|
+
return "README.md files are indexes, not Truth Sync targets. Keep engineering truth in bounded behavior, contract, architecture, workflow, operations, and test docs.";
|
|
1488
|
+
};
|
|
1489
|
+
var titleCase = (value) => {
|
|
1490
|
+
return value.split(/[-_\s]+/u).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
|
|
1491
|
+
};
|
|
1492
|
+
var renderHierarchicalAreasIndexTemplate = (config) => {
|
|
1493
|
+
const defaultArea = config.truthmark.routes.defaultArea;
|
|
1494
|
+
const childPath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
|
|
1495
|
+
const title = titleCase(defaultArea);
|
|
1496
|
+
const sourceOfTruth = resolveRelativePath(
|
|
1497
|
+
config.truthmark.paths.routesIndex,
|
|
1498
|
+
".truthmark/config.yml"
|
|
1913
1499
|
);
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1500
|
+
return [
|
|
1501
|
+
"---",
|
|
1502
|
+
"status: active",
|
|
1503
|
+
"doc_type: route-index",
|
|
1504
|
+
`last_reviewed: ${currentDate()}`,
|
|
1505
|
+
"---",
|
|
1506
|
+
"",
|
|
1507
|
+
"# Truthmark Areas",
|
|
1508
|
+
"",
|
|
1509
|
+
`## ${title}`,
|
|
1510
|
+
"",
|
|
1511
|
+
"Area files:",
|
|
1512
|
+
`- ${childPath}`,
|
|
1513
|
+
"",
|
|
1514
|
+
"Code surface:",
|
|
1515
|
+
"- src/**",
|
|
1516
|
+
"",
|
|
1517
|
+
"Update truth when:",
|
|
1518
|
+
"- behavior changes affect the routed truth documents",
|
|
1519
|
+
"- API contracts or current feature behavior changes",
|
|
1520
|
+
"",
|
|
1521
|
+
"## Source References",
|
|
1522
|
+
"",
|
|
1523
|
+
`- ${sourceOfTruth}`,
|
|
1524
|
+
""
|
|
1525
|
+
].join("\n");
|
|
1526
|
+
};
|
|
1527
|
+
var renderChildAreaTemplate = (config) => {
|
|
1528
|
+
const defaultArea = config.truthmark.routes.defaultArea;
|
|
1529
|
+
const title = titleCase(defaultArea);
|
|
1530
|
+
const truthDocsRoot = truthRoot(config);
|
|
1531
|
+
const bootstrapTruthDoc = `${truthDocsRoot}/${defaultArea}/bootstrap-routing.md`;
|
|
1532
|
+
const templatePath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
|
|
1533
|
+
const sourceOfTruth = resolveRelativePath(
|
|
1534
|
+
templatePath,
|
|
1535
|
+
".truthmark/config.yml"
|
|
1940
1536
|
);
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1537
|
+
return [
|
|
1538
|
+
"---",
|
|
1539
|
+
"status: active",
|
|
1540
|
+
"doc_type: area-route",
|
|
1541
|
+
`last_reviewed: ${currentDate()}`,
|
|
1542
|
+
"---",
|
|
1543
|
+
"",
|
|
1544
|
+
`# ${title} Areas`,
|
|
1545
|
+
"",
|
|
1546
|
+
`## ${title}`,
|
|
1547
|
+
"",
|
|
1548
|
+
"Truth documents:",
|
|
1549
|
+
"```yaml",
|
|
1550
|
+
"truth_documents:",
|
|
1551
|
+
` - path: ${bootstrapTruthDoc}`,
|
|
1552
|
+
" kind: engineering-workflow",
|
|
1553
|
+
" lane: engineering",
|
|
1554
|
+
"```",
|
|
1555
|
+
"",
|
|
1556
|
+
"This is a provisional bootstrap route. It exists only to make fresh repositories routeable until real product, service, domain, or ownership areas are created.",
|
|
1557
|
+
"",
|
|
1558
|
+
"Code surface:",
|
|
1559
|
+
"- src/**",
|
|
1560
|
+
"",
|
|
1561
|
+
"Update truth when:",
|
|
1562
|
+
"- this provisional bootstrap route is the only match for a real code surface",
|
|
1563
|
+
"- route ownership is still broad, mixed, or ambiguous",
|
|
1564
|
+
"- Run Truth Structure before normal Truth Sync so the touched code gets a bounded owner",
|
|
1565
|
+
"",
|
|
1566
|
+
"## Source References",
|
|
1567
|
+
"",
|
|
1568
|
+
`- ${sourceOfTruth}`,
|
|
1569
|
+
""
|
|
1570
|
+
].join("\n");
|
|
1958
1571
|
};
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1572
|
+
var renderBootstrapRoutingDocTemplate = (config) => {
|
|
1573
|
+
const defaultArea = config.truthmark.routes.defaultArea;
|
|
1574
|
+
const title = titleCase(defaultArea);
|
|
1575
|
+
const templatePath = `${truthRoot(config)}/${defaultArea}/bootstrap-routing.md`;
|
|
1576
|
+
const routePath = `${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`;
|
|
1577
|
+
const routeSource = resolveRelativePath(templatePath, routePath);
|
|
1578
|
+
const configSource = resolveRelativePath(
|
|
1579
|
+
templatePath,
|
|
1580
|
+
".truthmark/config.yml"
|
|
1581
|
+
);
|
|
1582
|
+
const today = currentDate();
|
|
1962
1583
|
return [
|
|
1963
|
-
"
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1584
|
+
"---",
|
|
1585
|
+
"status: active",
|
|
1586
|
+
"truth_kind: engineering-workflow",
|
|
1587
|
+
`last_reviewed: ${today}`,
|
|
1588
|
+
"---",
|
|
1589
|
+
"",
|
|
1590
|
+
`# ${title} Bootstrap Routing`,
|
|
1591
|
+
"",
|
|
1592
|
+
"## Purpose",
|
|
1593
|
+
"",
|
|
1594
|
+
`This doc records the provisional broad route for ${defaultArea}.`,
|
|
1595
|
+
"This doc is a bootstrap handoff, not a behavior truth dumping ground.",
|
|
1596
|
+
"It is not a substitute for bounded product and engineering truth docs.",
|
|
1597
|
+
"",
|
|
1598
|
+
"## Scope",
|
|
1599
|
+
"",
|
|
1600
|
+
"This doc owns only the initial routing workflow for a fresh Truthmark repository whose default route still maps a broad code surface such as `src/**`.",
|
|
1601
|
+
"It does not own implementation behavior under that code surface.",
|
|
1602
|
+
"",
|
|
1603
|
+
"## Current Implementation Behavior",
|
|
1604
|
+
"",
|
|
1605
|
+
"The scaffold creates this provisional bootstrap handoff only when a default broad route needs a canonical owner. Agents use it as a signal to run Truth Structure and create bounded routes before normal Truth Sync, not as a place to accumulate implementation claims.",
|
|
1606
|
+
"",
|
|
1607
|
+
"## Product Truth Links",
|
|
1608
|
+
"",
|
|
1609
|
+
"- None. This is an engineering bootstrap handoff for routing setup, not a product promise.",
|
|
1610
|
+
"",
|
|
1611
|
+
"## Triggers",
|
|
1612
|
+
"",
|
|
1613
|
+
"- A real code change maps only to this provisional broad route.",
|
|
1614
|
+
"- Truth Sync cannot identify a specific behavior-owned route and bounded truth owner.",
|
|
1615
|
+
"- A maintainer or agent is onboarding the first real product, service, domain, package, or ownership area.",
|
|
1616
|
+
"",
|
|
1617
|
+
"## Inputs",
|
|
1618
|
+
"",
|
|
1619
|
+
"- Current route files under the configured Truthmark route root.",
|
|
1620
|
+
"- The touched code, tests, configuration, and existing docs needed to infer the smallest real owner.",
|
|
1621
|
+
"- Repository instruction files that exist in the checkout.",
|
|
1622
|
+
"",
|
|
1623
|
+
"## Execution Model",
|
|
1624
|
+
"",
|
|
1625
|
+
"Run Truth Structure before normal Truth Sync when real code changes touch only this broad route. Truth Structure should create or repair bounded areas first; Truth Sync should then update the bounded owner docs.",
|
|
1626
|
+
"",
|
|
1627
|
+
"## Steps",
|
|
1628
|
+
"",
|
|
1629
|
+
"1. Treat this route as provisional and insufficient for normal behavior maintenance.",
|
|
1630
|
+
"2. Inspect the touched code/test surface and infer the narrowest durable owner.",
|
|
1631
|
+
"3. Create or repair route entries and truth docs for that owner.",
|
|
1632
|
+
"4. Leave this bootstrap doc small; do not append behavior details here.",
|
|
1633
|
+
"5. Resume Truth Sync only after the touched code resolves to a bounded owner.",
|
|
1634
|
+
"",
|
|
1635
|
+
"## State, Retry, And Failure Behavior",
|
|
1636
|
+
"",
|
|
1637
|
+
"If ownership cannot be inferred safely, stop and report manual-review files instead of widening this route or adding generic behavior prose.",
|
|
1638
|
+
"",
|
|
1639
|
+
"## Outputs",
|
|
1640
|
+
"",
|
|
1641
|
+
"- Bounded route areas and lane-appropriate truth docs for the touched surface.",
|
|
1642
|
+
"- A compact manual handoff report when ownership remains ambiguous.",
|
|
1643
|
+
"",
|
|
1644
|
+
"## Engineering Decisions",
|
|
1645
|
+
"",
|
|
1646
|
+
`- Decision (${today}): Default broad routing is provisional bootstrap state. Agents should create bounded areas before normal Truth Sync rather than extending a catch-all overview doc.`,
|
|
1647
|
+
"",
|
|
1648
|
+
"## Rationale",
|
|
1649
|
+
"",
|
|
1650
|
+
"Scoped ownership keeps agent context close to affected files and prevents broad default docs from absorbing unrelated behavior. This preserves agent-native truth maintenance without adding a token-heavy discovery layer.",
|
|
1651
|
+
"",
|
|
1652
|
+
"## Non-Goals",
|
|
1653
|
+
"",
|
|
1654
|
+
"- This doc is not a repository behavior overview.",
|
|
1655
|
+
"- This doc is not a product capability or engineering behavior owner.",
|
|
1656
|
+
"- This doc is not a permanent home for claims about files under `src/**`.",
|
|
1657
|
+
"",
|
|
1658
|
+
"## Maintenance Notes",
|
|
1659
|
+
"",
|
|
1660
|
+
"Keep this doc short. When a repository has real bounded routes, prefer updating those routes and their truth docs instead of expanding this bootstrap handoff.",
|
|
1661
|
+
"",
|
|
1662
|
+
"## Source References",
|
|
1663
|
+
"",
|
|
1664
|
+
`- ${routeSource}`,
|
|
1665
|
+
`- ${configSource}`,
|
|
1666
|
+
""
|
|
1971
1667
|
].join("\n");
|
|
1972
1668
|
};
|
|
1973
|
-
var
|
|
1669
|
+
var renderTruthRootReadmeTemplate = (config = createDefaultConfig(), lane = "engineering") => {
|
|
1670
|
+
const templatePath = `${lane === "product" ? resolveProductTruthRoot(config) : resolveEngineeringTruthRoot(config)}/README.md`;
|
|
1671
|
+
const sourceOfTruth = resolveRelativePath(
|
|
1672
|
+
templatePath,
|
|
1673
|
+
config.truthmark.paths.routesIndex
|
|
1674
|
+
);
|
|
1974
1675
|
return [
|
|
1975
|
-
"
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1676
|
+
"---",
|
|
1677
|
+
"status: active",
|
|
1678
|
+
"doc_type: index",
|
|
1679
|
+
`last_reviewed: ${currentDate()}`,
|
|
1680
|
+
"---",
|
|
1681
|
+
"",
|
|
1682
|
+
"# Truth Docs",
|
|
1683
|
+
"",
|
|
1684
|
+
"This directory is an index for current truth docs organized by the configured Truthmark hierarchy.",
|
|
1685
|
+
"",
|
|
1686
|
+
renderLaneRootReadmeSummary(lane),
|
|
1687
|
+
"",
|
|
1688
|
+
renderLaneRootLeafDocGuidance(lane),
|
|
1689
|
+
"",
|
|
1690
|
+
"## Source References",
|
|
1691
|
+
"",
|
|
1692
|
+
`- ${sourceOfTruth}`,
|
|
1693
|
+
""
|
|
1984
1694
|
].join("\n");
|
|
1985
1695
|
};
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
const
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
return lines.split(/\n/u).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => `${indent}${renderBulletLine(line)}`).join("\n");
|
|
1994
|
-
};
|
|
1995
|
-
var renderLaneClassificationRuleBlock = (config = defaultAgentConfig(), indent = " ") => {
|
|
1996
|
-
const [, ...rules] = renderLaneClassificationInstructions(config).split(/\n/u);
|
|
1997
|
-
return renderBulletBlock(rules.join("\n"), indent);
|
|
1998
|
-
};
|
|
1999
|
-
var renderReadOnlyLaneClassificationRuleBlock = (config = defaultAgentConfig(), indent = " ") => {
|
|
2000
|
-
const productTruthRoot = resolveProductTruthRoot(config);
|
|
2001
|
-
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
2002
|
-
return renderBulletBlock(
|
|
2003
|
-
[
|
|
2004
|
-
"classify the request or changed surface as product-lane, engineering-lane, both-lane, or ambiguous for reporting only",
|
|
2005
|
-
`product-lane ownership belongs under ${productTruthRoot} and describes product promises, boundaries, rationale, decisions, and success criteria`,
|
|
2006
|
-
`engineering-lane ownership belongs under ${engineeringTruthRoot} and describes source-backed current realization, contracts, architecture, workflows, operations, or tests`,
|
|
2007
|
-
"both-lane ownership uses separate product and engineering docs cross-linked in route YAML with realized_by and realizes, not in doc frontmatter",
|
|
2008
|
-
"ambiguous lane ownership should be reported for manual handoff or routed to Truth Structure",
|
|
2009
|
-
LANE_INVARIANT
|
|
2010
|
-
].join("\n"),
|
|
2011
|
-
indent
|
|
1696
|
+
var renderTruthDomainReadmeTemplate = (config) => {
|
|
1697
|
+
const defaultArea = config.truthmark.routes.defaultArea;
|
|
1698
|
+
const title = titleCase(defaultArea);
|
|
1699
|
+
const templatePath = `${truthRoot(config)}/${defaultArea}/README.md`;
|
|
1700
|
+
const sourceOfTruth = resolveRelativePath(
|
|
1701
|
+
templatePath,
|
|
1702
|
+
`${config.truthmark.paths.routeAreasRoot}/${defaultArea}.md`
|
|
2012
1703
|
);
|
|
2013
|
-
};
|
|
2014
|
-
var DECISION_TRUTH_INSTRUCTIONS = [
|
|
2015
|
-
"Decision truth lives in the canonical doc it governs; date active decisions inline when added or changed.",
|
|
2016
|
-
"Do not create separate active-decision ADR/planning logs; replace the active decision and let Git history carry the audit trail.",
|
|
2017
|
-
"Product decisions belong in product truth; engineering, architecture, contract, workflow, and operational decisions belong in engineering truth."
|
|
2018
|
-
].join("\n");
|
|
2019
|
-
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.";
|
|
2020
|
-
var renderLaneClassificationInstructions = (config = defaultAgentConfig()) => {
|
|
2021
|
-
const productTruthRoot = resolveProductTruthRoot(config);
|
|
2022
|
-
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
2023
1704
|
return [
|
|
2024
|
-
"
|
|
2025
|
-
"
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
"
|
|
2029
|
-
"
|
|
2030
|
-
|
|
1705
|
+
"---",
|
|
1706
|
+
"status: active",
|
|
1707
|
+
"doc_type: index",
|
|
1708
|
+
`last_reviewed: ${currentDate()}`,
|
|
1709
|
+
"---",
|
|
1710
|
+
"",
|
|
1711
|
+
`# ${title} Truth Docs`,
|
|
1712
|
+
"",
|
|
1713
|
+
`This directory indexes bounded ${title.toLowerCase()} truth docs.`,
|
|
1714
|
+
"",
|
|
1715
|
+
"README.md files are indexes, not Truth Sync targets. Keep bounded truth in leaf docs in this directory.",
|
|
1716
|
+
"",
|
|
1717
|
+
"Current leaf docs:",
|
|
1718
|
+
"",
|
|
1719
|
+
"- [Bootstrap routing](bootstrap-routing.md)",
|
|
1720
|
+
"",
|
|
1721
|
+
"## Source References",
|
|
1722
|
+
"",
|
|
1723
|
+
`- ${sourceOfTruth}`,
|
|
1724
|
+
""
|
|
2031
1725
|
].join("\n");
|
|
2032
1726
|
};
|
|
2033
|
-
var
|
|
2034
|
-
"Repository instruction files and explicitly configured policy docs remain instruction authority when present; do not assume a repository uses any particular policy path.",
|
|
2035
|
-
"Implementation code and canonical truth docs are inspected evidence for current behavior; they do not silently override workflow write boundaries."
|
|
2036
|
-
].join("\n");
|
|
2037
|
-
var REPOSITORY_INTELLIGENCE_INSTRUCTIONS = [
|
|
2038
|
-
"Repository intelligence artifacts are optional derived context: RepoIndex, RouteMap, ImpactSet, and WorkflowState/action context may guide routing, write boundaries, and verification planning when available.",
|
|
2039
|
-
"They do not override checkout evidence, canonical truth docs, route files, or workflow write boundaries.",
|
|
2040
|
-
"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."
|
|
2041
|
-
].join("\n");
|
|
2042
|
-
var FEATURE_DOC_TEMPLATE_INSTRUCTIONS = [
|
|
2043
|
-
"When creating or updating a truth doc, inspect the routed truth kind and use the matching template under the configured Truthmark templates root.",
|
|
2044
|
-
"Supported kinds: product-capability, engineering-behavior, engineering-contract, engineering-architecture, engineering-workflow, engineering-operations, and engineering-test-behavior.",
|
|
2045
|
-
"Treat the HTML comments under each template section as normative authoring guidance for that section.",
|
|
2046
|
-
"Align existing docs to that template and write or repair section content so it satisfies the comment guidance while preserving accurate authored content.",
|
|
2047
|
-
"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.",
|
|
2048
|
-
"Teams may edit template files under the configured Truthmark templates root to define their local truth-doc standards."
|
|
2049
|
-
].join("\n");
|
|
2050
|
-
var TRUTH_DOC_AUTHORING_STYLE_INSTRUCTIONS = [
|
|
2051
|
-
"Truth-doc prose style:",
|
|
2052
|
-
"- Use professional, plain technical prose. Prefer specific current-state claims over promotional, symbolic, or generic significance language.",
|
|
2053
|
-
"- Avoid common AI-writing tells: pivotal, crucial, underscores, serves as, stands as, showcases, landscape, vague expert attributions, and generic upbeat conclusions.",
|
|
2054
|
-
"- Keep claims evidence-backed and diff-friendly: one durable claim per bullet or line; paragraphs should be no longer than one or two short sentences.",
|
|
2055
|
-
"- Do not add personality, rhetorical flourish, first-person commentary, or marketing tone.",
|
|
2056
|
-
"- Rewrite dense or formulaic prose only when it improves readability without removing scope, evidence, decisions, or source references."
|
|
2057
|
-
].join("\n");
|
|
2058
|
-
var renderTruthDocOwnershipGateSection = (subject, outcome) => {
|
|
1727
|
+
var renderTemplateSection = (section) => {
|
|
2059
1728
|
return [
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
"
|
|
2063
|
-
|
|
2064
|
-
"
|
|
2065
|
-
|
|
1729
|
+
section.heading,
|
|
1730
|
+
"",
|
|
1731
|
+
"<!--",
|
|
1732
|
+
...section.guidance,
|
|
1733
|
+
"-->",
|
|
1734
|
+
"",
|
|
1735
|
+
`{{${section.placeholder}}}`,
|
|
1736
|
+
""
|
|
1737
|
+
];
|
|
2066
1738
|
};
|
|
2067
|
-
var
|
|
2068
|
-
"
|
|
2069
|
-
"- 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",
|
|
2070
|
-
"- 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",
|
|
2071
|
-
"- 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",
|
|
2072
|
-
"- if ownership of a decision or rationale is unclear, stop with manual-review files instead of deleting it or guessing",
|
|
2073
|
-
"- 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"
|
|
2074
|
-
].join("\n");
|
|
2075
|
-
var renderTruthDocRestructureGateSection = (scope) => {
|
|
2076
|
-
return [
|
|
2077
|
-
"Truth-doc shape repair review:",
|
|
2078
|
-
`- ${scope}`,
|
|
2079
|
-
"- repair shape in place only after the ownership review confirms the doc is the right bounded owner",
|
|
2080
|
-
"- use Truth Structure for ownership splits; do not treat broad or mixed-owner docs as in-place repair work",
|
|
2081
|
-
"- repair shape when a narrow edit would make truth worse: missing template sections, stale evidence conflicts, cross-section updates within one owner, or wrong frontmatter/source/headings",
|
|
2082
|
-
"- preserve supported claims; remove, narrow, or record unsupported or stale claims for manual handoff",
|
|
2083
|
-
"- report docs restructured and why a narrow edit was not sufficient"
|
|
2084
|
-
].join("\n");
|
|
1739
|
+
var titleToPlaceholder = (title) => {
|
|
1740
|
+
return title.replace(/^#+\s+/u, "").toLowerCase().replaceAll(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2085
1741
|
};
|
|
2086
|
-
var
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
1742
|
+
var findTemplateSectionHeadings = (template) => {
|
|
1743
|
+
const matches = [];
|
|
1744
|
+
let fencedCodeMarker = null;
|
|
1745
|
+
let fencedCodeLength = 0;
|
|
1746
|
+
for (const lineMatch of template.matchAll(/^.*(?:\r?\n|$)/gm)) {
|
|
1747
|
+
const rawLine = lineMatch[0];
|
|
1748
|
+
if (rawLine.length === 0) {
|
|
1749
|
+
continue;
|
|
1750
|
+
}
|
|
1751
|
+
const line = rawLine.replace(/\r?\n$/u, "");
|
|
1752
|
+
const fenceMatch = /^(?: {0,3})(`{3,}|~{3,})/u.exec(line);
|
|
1753
|
+
if (fenceMatch) {
|
|
1754
|
+
const marker = fenceMatch[1]?.[0];
|
|
1755
|
+
const length = fenceMatch[1]?.length ?? 0;
|
|
1756
|
+
if (fencedCodeMarker === null) {
|
|
1757
|
+
fencedCodeMarker = marker;
|
|
1758
|
+
fencedCodeLength = length;
|
|
1759
|
+
} else if (marker === fencedCodeMarker && length >= fencedCodeLength) {
|
|
1760
|
+
fencedCodeMarker = null;
|
|
1761
|
+
fencedCodeLength = 0;
|
|
1762
|
+
}
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
if (fencedCodeMarker === null && /^## .+$/u.test(line)) {
|
|
1766
|
+
matches.push({ heading: line.trim(), index: lineMatch.index });
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return matches;
|
|
2100
1770
|
};
|
|
2101
|
-
var
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
1771
|
+
var parseTemplateSections = (template) => {
|
|
1772
|
+
const matches = findTemplateSectionHeadings(template);
|
|
1773
|
+
if (matches.length === 0) {
|
|
1774
|
+
return { preamble: template.trimEnd(), sections: [] };
|
|
1775
|
+
}
|
|
1776
|
+
const sections = matches.map((match, index) => {
|
|
1777
|
+
const start = match.index;
|
|
1778
|
+
const next = matches[index + 1];
|
|
1779
|
+
const end = next?.index ?? template.length;
|
|
1780
|
+
return {
|
|
1781
|
+
heading: match.heading,
|
|
1782
|
+
block: template.slice(start, end).trimEnd()
|
|
1783
|
+
};
|
|
1784
|
+
});
|
|
1785
|
+
return {
|
|
1786
|
+
preamble: template.slice(0, matches[0]?.index ?? 0).trimEnd(),
|
|
1787
|
+
sections
|
|
1788
|
+
};
|
|
2108
1789
|
};
|
|
2109
|
-
var
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
].join("\n");
|
|
1790
|
+
var LEGACY_MANAGED_TEMPLATE_HEADINGS = /* @__PURE__ */ new Map([
|
|
1791
|
+
["## Current Behavior", "## Current Implementation Behavior"],
|
|
1792
|
+
["## Source Evidence", "## Source References"]
|
|
1793
|
+
]);
|
|
1794
|
+
var resolveManagedTemplateHeading = (heading) => {
|
|
1795
|
+
return LEGACY_MANAGED_TEMPLATE_HEADINGS.get(heading) ?? heading;
|
|
2116
1796
|
};
|
|
2117
|
-
var
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
1797
|
+
var stripManagedFrontmatterFields = (preamble) => {
|
|
1798
|
+
if (!preamble.startsWith("---\n")) {
|
|
1799
|
+
return preamble;
|
|
1800
|
+
}
|
|
1801
|
+
const lines = preamble.split("\n");
|
|
1802
|
+
const closingIndex = lines.findIndex(
|
|
1803
|
+
(line, index) => index > 0 && line.trim() === "---"
|
|
1804
|
+
);
|
|
1805
|
+
if (closingIndex < 0) {
|
|
1806
|
+
return preamble;
|
|
1807
|
+
}
|
|
1808
|
+
const fieldsToRemove = /* @__PURE__ */ new Set(["source_of_truth", "doc_type", "truth_lane"]);
|
|
1809
|
+
const keptFrontmatterLines = [];
|
|
1810
|
+
let skippingManagedField = false;
|
|
1811
|
+
for (const line of lines.slice(1, closingIndex)) {
|
|
1812
|
+
const keyMatch = /^([A-Za-z0-9_-]+):(\s|$)/u.exec(line);
|
|
1813
|
+
if (keyMatch) {
|
|
1814
|
+
skippingManagedField = fieldsToRemove.has(keyMatch[1] ?? "");
|
|
1815
|
+
}
|
|
1816
|
+
if (!skippingManagedField) {
|
|
1817
|
+
keptFrontmatterLines.push(line);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
2126
1820
|
return [
|
|
2127
|
-
"
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
...writeAgentLines,
|
|
2133
|
-
`- ${parentRule}`
|
|
2134
|
-
].join("\n");
|
|
1821
|
+
"---",
|
|
1822
|
+
...keptFrontmatterLines,
|
|
1823
|
+
"---",
|
|
1824
|
+
...lines.slice(closingIndex + 1)
|
|
1825
|
+
].join("\n").trimEnd();
|
|
2135
1826
|
};
|
|
2136
|
-
var
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
1827
|
+
var mergeTruthDocTemplate = (existingTemplate, defaultTemplate) => {
|
|
1828
|
+
if (existingTemplate.trim().length === 0) {
|
|
1829
|
+
return defaultTemplate;
|
|
1830
|
+
}
|
|
1831
|
+
const defaultParsed = parseTemplateSections(defaultTemplate);
|
|
1832
|
+
const existingParsed = parseTemplateSections(existingTemplate);
|
|
1833
|
+
const defaultHeadings = new Set(
|
|
1834
|
+
defaultParsed.sections.map((section) => section.heading)
|
|
2140
1835
|
);
|
|
2141
|
-
const
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
1836
|
+
const customBeforeDefault = /* @__PURE__ */ new Map();
|
|
1837
|
+
const trailingCustomSections = [];
|
|
1838
|
+
existingParsed.sections.forEach((section, index) => {
|
|
1839
|
+
if (defaultHeadings.has(resolveManagedTemplateHeading(section.heading))) {
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
const nextDefaultSection = existingParsed.sections.slice(index + 1).find(
|
|
1843
|
+
(candidate) => defaultHeadings.has(resolveManagedTemplateHeading(candidate.heading))
|
|
1844
|
+
);
|
|
1845
|
+
if (nextDefaultSection) {
|
|
1846
|
+
const nextDefaultHeading = resolveManagedTemplateHeading(
|
|
1847
|
+
nextDefaultSection.heading
|
|
1848
|
+
);
|
|
1849
|
+
const bucket = customBeforeDefault.get(nextDefaultHeading) ?? [];
|
|
1850
|
+
bucket.push(section);
|
|
1851
|
+
customBeforeDefault.set(nextDefaultHeading, bucket);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
trailingCustomSections.push(section);
|
|
1855
|
+
});
|
|
1856
|
+
const mergedSections = defaultParsed.sections.flatMap((section) => [
|
|
1857
|
+
...customBeforeDefault.get(section.heading) ?? [],
|
|
1858
|
+
section
|
|
1859
|
+
]);
|
|
2149
1860
|
return [
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
...writeAgentLines,
|
|
2156
|
-
`- ${parentRule}`
|
|
2157
|
-
].join("\n");
|
|
1861
|
+
stripManagedFrontmatterFields(existingParsed.preamble),
|
|
1862
|
+
...mergedSections.map((section) => section.block),
|
|
1863
|
+
...trailingCustomSections.map((section) => section.block),
|
|
1864
|
+
""
|
|
1865
|
+
].filter((block) => block.length > 0).join("\n\n");
|
|
2158
1866
|
};
|
|
2159
|
-
var
|
|
2160
|
-
const mentions = agents.map(
|
|
2161
|
-
(agent) => `${agent.replace(/_/gu, "-")} subagent`
|
|
2162
|
-
);
|
|
2163
|
-
const writeMentions = writeAgents.map(
|
|
2164
|
-
(agent) => `${agent.replace(/_/gu, "-")} subagent`
|
|
2165
|
-
);
|
|
2166
|
-
const writeAgentLines = writeMentions.length > 0 ? [
|
|
2167
|
-
`- dispatch write-capable project subagents only with explicit write leases: ${writeMentions.join(", ")}`,
|
|
2168
|
-
"- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
|
|
2169
|
-
"- write workers must stop when a required edit is off-lease and report status, filesChanged, evidence, offLeaseChanges, blockers, and notes",
|
|
2170
|
-
"- parent must inspect the actual checkout diff against each lease before accepting a worker report"
|
|
2171
|
-
] : [];
|
|
2172
|
-
const readOnlyScope = writeAgents.length > 0 ? "for verification" : "only";
|
|
2173
|
-
const readOnlySubagentLabel = writeAgents.length > 0 ? "read-only subagents" : "subagents";
|
|
1867
|
+
var renderBehaviorDocTemplateFile = () => {
|
|
2174
1868
|
return [
|
|
2175
|
-
"
|
|
2176
|
-
"
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
1869
|
+
"---",
|
|
1870
|
+
"status: active",
|
|
1871
|
+
"truth_kind: engineering-behavior",
|
|
1872
|
+
`last_reviewed: ${currentDate()}`,
|
|
1873
|
+
"---",
|
|
1874
|
+
"",
|
|
1875
|
+
"# {{title}}",
|
|
1876
|
+
"",
|
|
1877
|
+
"## Purpose",
|
|
1878
|
+
"",
|
|
1879
|
+
"<!--",
|
|
1880
|
+
"State the user/system outcome this behavior protects and why it exists.",
|
|
1881
|
+
"Include the problem boundary and durable value; exclude roadmap, implementation plan, and historical narrative.",
|
|
1882
|
+
"List the code, config, docs, or tests that support the claim in Source References rather than prose-only assertion.",
|
|
1883
|
+
"-->",
|
|
1884
|
+
"",
|
|
1885
|
+
"{{purpose}}",
|
|
1886
|
+
"",
|
|
1887
|
+
"## Scope",
|
|
1888
|
+
"",
|
|
1889
|
+
"<!--",
|
|
1890
|
+
"Define the one coherent behavior surface this document owns.",
|
|
1891
|
+
"Include in-scope actors, entrypoints, state/data owned by this doc, and explicit handoffs to neighboring truth docs.",
|
|
1892
|
+
"Split into another leaf doc when content introduces a distinct outcome, state machine, rule family, external contract, or route owner.",
|
|
1893
|
+
"Keep README.md files as indexes only.",
|
|
1894
|
+
"-->",
|
|
1895
|
+
"",
|
|
1896
|
+
"{{scope}}",
|
|
1897
|
+
"",
|
|
1898
|
+
"This doc was created from the editable engineering-behavior template at {{template_path}}.",
|
|
1899
|
+
"",
|
|
1900
|
+
"## Current Implementation Behavior",
|
|
1901
|
+
"",
|
|
1902
|
+
"<!--",
|
|
1903
|
+
"Describe only current implemented behavior in present tense.",
|
|
1904
|
+
"Cover observable behavior, important defaults, and user/system-visible effects; exclude desired future behavior and speculative design.",
|
|
1905
|
+
"Every non-obvious claim should be checkable from Source References.",
|
|
1906
|
+
"-->",
|
|
1907
|
+
"",
|
|
1908
|
+
"{{current_implementation_behavior}}",
|
|
1909
|
+
"",
|
|
1910
|
+
"## Core Rules",
|
|
1911
|
+
"",
|
|
1912
|
+
"<!--",
|
|
1913
|
+
"Capture stable business rules, invariants, precedence rules, validation rules, and must-never constraints.",
|
|
1914
|
+
"Separate rules from incidental implementation details; cite current implementation or tests for rule enforcement.",
|
|
1915
|
+
"-->",
|
|
1916
|
+
"",
|
|
1917
|
+
"{{core_rules}}",
|
|
1918
|
+
"",
|
|
1919
|
+
"## Behavior Scenarios",
|
|
1920
|
+
"",
|
|
1921
|
+
"<!--",
|
|
1922
|
+
"Use compact scenario blocks only where they clarify normal, fallback, or compatibility-critical behavior.",
|
|
1923
|
+
"Write scenarios as current truth, not desired requirements: `#### Scenario: <implemented case>` followed by `- **GIVEN** ...`, `- **WHEN** ...`, `- **THEN** ...`, and optional `- **AND** ...` bullets.",
|
|
1924
|
+
"Keep each bullet evidence-backed and observable; do not force a scenario for every rule.",
|
|
1925
|
+
"-->",
|
|
1926
|
+
"",
|
|
1927
|
+
"{{behavior_scenarios}}",
|
|
1928
|
+
"",
|
|
1929
|
+
"## Flows And States",
|
|
1930
|
+
"",
|
|
1931
|
+
"<!--",
|
|
1932
|
+
"Document state transitions, lifecycle stages, retries, fallbacks, route switches, and important error paths.",
|
|
1933
|
+
"State 'None beyond current behavior.' when this behavior has no distinct flow or state model.",
|
|
1934
|
+
"-->",
|
|
1935
|
+
"",
|
|
1936
|
+
"{{flows_and_states}}",
|
|
1937
|
+
"",
|
|
1938
|
+
"## Contracts",
|
|
1939
|
+
"",
|
|
1940
|
+
"<!--",
|
|
1941
|
+
"Capture user-visible or integration contracts: CLI/API shape, inputs, outputs, diagnostics, files, events, permissions, or links to canonical contract docs.",
|
|
1942
|
+
"Avoid duplicating a separate canonical contract doc; link to it when contract ownership lives elsewhere.",
|
|
1943
|
+
"-->",
|
|
1944
|
+
"",
|
|
1945
|
+
"{{contracts}}",
|
|
1946
|
+
"",
|
|
1947
|
+
"## Product Truth Links",
|
|
1948
|
+
"",
|
|
1949
|
+
"<!--",
|
|
1950
|
+
"List product truth docs this engineering doc realizes; author canonical realizes links in route YAML, not doc frontmatter.",
|
|
1951
|
+
"Use 'None.' when this is purely internal engineering behavior.",
|
|
1952
|
+
"-->",
|
|
1953
|
+
"",
|
|
1954
|
+
"{{product_truth_links}}",
|
|
1955
|
+
"",
|
|
1956
|
+
"## Engineering Decisions",
|
|
1957
|
+
"",
|
|
1958
|
+
"<!--",
|
|
1959
|
+
"Keep active decisions only, dated inline when added or changed.",
|
|
1960
|
+
"Explain decisions that shape behavior, boundaries, rejected alternatives, or migration constraints; replace stale decisions instead of appending historical logs.",
|
|
1961
|
+
"-->",
|
|
1962
|
+
"",
|
|
1963
|
+
"{{engineering_decisions}}",
|
|
1964
|
+
"",
|
|
1965
|
+
"## Rationale",
|
|
1966
|
+
"",
|
|
1967
|
+
"<!--",
|
|
1968
|
+
"Explain why the current behavior and active decisions are this way, including tradeoffs and constraints.",
|
|
1969
|
+
"Tie rationale to evidence-backed behavior; do not use this as a changelog.",
|
|
1970
|
+
"-->",
|
|
1971
|
+
"",
|
|
1972
|
+
"{{rationale}}",
|
|
1973
|
+
"",
|
|
1974
|
+
"## Non-Goals",
|
|
1975
|
+
"",
|
|
1976
|
+
"<!--",
|
|
1977
|
+
"Name adjacent behavior this doc intentionally does not own, especially tempting future expansions or neighboring route owners.",
|
|
1978
|
+
"Use this section to prevent scope creep and duplicate truth ownership.",
|
|
1979
|
+
"-->",
|
|
1980
|
+
"",
|
|
1981
|
+
"{{non_goals}}",
|
|
1982
|
+
"",
|
|
1983
|
+
"## Maintenance Notes",
|
|
1984
|
+
"",
|
|
1985
|
+
"<!--",
|
|
1986
|
+
"List related tests, routing cautions, migration notes, evidence drift risks, and review triggers for future maintainers or agents.",
|
|
1987
|
+
"Keep this operational and current-state focused, not historical.",
|
|
1988
|
+
"-->",
|
|
1989
|
+
"",
|
|
1990
|
+
"{{maintenance_notes}}",
|
|
1991
|
+
"",
|
|
1992
|
+
"## Source References",
|
|
1993
|
+
"",
|
|
1994
|
+
"<!--",
|
|
1995
|
+
"List source files, tests, configs, generated templates, route files, or product instructions that support current claims.",
|
|
1996
|
+
"-->",
|
|
1997
|
+
"",
|
|
1998
|
+
"{{source_references}}",
|
|
1999
|
+
""
|
|
2182
2000
|
].join("\n");
|
|
2183
2001
|
};
|
|
2184
|
-
var
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2002
|
+
var sectionSpec = (heading, guidance, placeholder = titleToPlaceholder(heading)) => ({ heading, guidance, placeholder });
|
|
2003
|
+
var PURPOSE_SECTION = sectionSpec("## Purpose", [
|
|
2004
|
+
"State the software-engineering outcome this document protects and why the documented surface exists.",
|
|
2005
|
+
"Include durable value, impacted users/systems, and the problem boundary; exclude roadmap, implementation plans, and historical narrative.",
|
|
2006
|
+
"Keep claims traceable to Source References rather than prose-only assertion."
|
|
2007
|
+
]);
|
|
2008
|
+
var SCOPE_SECTION = sectionSpec("## Scope", [
|
|
2009
|
+
"Define the one coherent surface this document owns, including actors, entrypoints, owned state/data, and handoffs to neighboring truth docs.",
|
|
2010
|
+
"Call out important out-of-scope boundaries here or in Non-Goals; split the doc when it mixes distinct outcomes, lifecycles, contracts, or owners."
|
|
2011
|
+
]);
|
|
2012
|
+
var PRODUCT_DECISIONS_SECTION = sectionSpec(
|
|
2013
|
+
"## Product Decisions",
|
|
2014
|
+
[
|
|
2015
|
+
"Keep active decisions only, dated inline when added or changed.",
|
|
2016
|
+
"Capture decisions that shape behavior, interfaces, boundaries, compatibility, risk acceptance, or migration constraints.",
|
|
2017
|
+
"Replace stale decisions instead of appending historical logs."
|
|
2018
|
+
],
|
|
2019
|
+
"decision"
|
|
2020
|
+
);
|
|
2021
|
+
var ENGINEERING_DECISIONS_SECTION = sectionSpec(
|
|
2022
|
+
"## Engineering Decisions",
|
|
2023
|
+
[
|
|
2024
|
+
"Keep active engineering, architecture, contract, workflow, or operational decisions only, dated inline when added or changed.",
|
|
2025
|
+
"Do not restate product promises, product rationale, or business decisions here; link product truth instead.",
|
|
2026
|
+
"Replace stale decisions instead of appending historical logs."
|
|
2027
|
+
],
|
|
2028
|
+
"engineering_decisions"
|
|
2029
|
+
);
|
|
2030
|
+
var RATIONALE_SECTION = sectionSpec("## Rationale", [
|
|
2031
|
+
"Explain why the current behavior, structure, or contract is this way, including tradeoffs and constraints.",
|
|
2032
|
+
"Tie rationale to evidence-backed facts and active decisions; do not use this as a changelog."
|
|
2033
|
+
]);
|
|
2034
|
+
var NON_GOALS_SECTION = sectionSpec("## Non-Goals", [
|
|
2035
|
+
"Name adjacent behavior, responsibilities, interfaces, or future expansions this doc intentionally does not own.",
|
|
2036
|
+
"Use this section to prevent scope creep and duplicate truth ownership."
|
|
2037
|
+
]);
|
|
2038
|
+
var MAINTENANCE_NOTES_SECTION = sectionSpec("## Maintenance Notes", [
|
|
2039
|
+
"List related tests, routing cautions, migration notes, compatibility risks, evidence drift risks, and review triggers for future maintainers or agents.",
|
|
2040
|
+
"Keep this operational and current-state focused, not historical."
|
|
2041
|
+
]);
|
|
2042
|
+
var SOURCE_REFERENCES_SECTION = sectionSpec(
|
|
2043
|
+
"## Source References",
|
|
2044
|
+
[
|
|
2045
|
+
"List source files, tests, configs, generated templates, route files, or product instructions that support current claims."
|
|
2046
|
+
],
|
|
2047
|
+
"source_references"
|
|
2048
|
+
);
|
|
2049
|
+
var renderTypedTruthDocTemplate = (truthKind, title, sections) => {
|
|
2197
2050
|
return [
|
|
2198
|
-
"
|
|
2199
|
-
"
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2051
|
+
"---",
|
|
2052
|
+
"status: active",
|
|
2053
|
+
`truth_kind: ${truthKind}`,
|
|
2054
|
+
`last_reviewed: ${currentDate()}`,
|
|
2055
|
+
"---",
|
|
2056
|
+
"",
|
|
2057
|
+
`# ${title}`,
|
|
2058
|
+
"",
|
|
2059
|
+
...renderTemplateSection(PURPOSE_SECTION),
|
|
2060
|
+
...renderTemplateSection(SCOPE_SECTION),
|
|
2061
|
+
...sections.flatMap(renderTemplateSection),
|
|
2062
|
+
...renderTemplateSection(ENGINEERING_DECISIONS_SECTION),
|
|
2063
|
+
...renderTemplateSection(RATIONALE_SECTION),
|
|
2064
|
+
...renderTemplateSection(NON_GOALS_SECTION),
|
|
2065
|
+
...renderTemplateSection(MAINTENANCE_NOTES_SECTION),
|
|
2066
|
+
...renderTemplateSection(SOURCE_REFERENCES_SECTION)
|
|
2205
2067
|
].join("\n");
|
|
2206
2068
|
};
|
|
2207
|
-
var
|
|
2208
|
-
|
|
2209
|
-
};
|
|
2210
|
-
var renderHierarchySummary = (config) => {
|
|
2211
|
-
const productRoot = resolveProductTruthRoot(config);
|
|
2212
|
-
const engineeringRoot = resolveEngineeringTruthRoot(config);
|
|
2069
|
+
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.";
|
|
2070
|
+
var renderProductTruthDocTemplate = (truthKind, title, sections, includeNonGoals) => {
|
|
2213
2071
|
return [
|
|
2214
|
-
"
|
|
2215
|
-
"
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2072
|
+
"---",
|
|
2073
|
+
"status: active",
|
|
2074
|
+
`truth_kind: ${truthKind}`,
|
|
2075
|
+
`last_reviewed: ${currentDate()}`,
|
|
2076
|
+
"---",
|
|
2077
|
+
"",
|
|
2078
|
+
`# ${title}`,
|
|
2079
|
+
"",
|
|
2080
|
+
"<!--",
|
|
2081
|
+
CORE_LANE_INVARIANT,
|
|
2082
|
+
"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.",
|
|
2083
|
+
"-->",
|
|
2084
|
+
"",
|
|
2085
|
+
...sections.flatMap(renderTemplateSection),
|
|
2086
|
+
...renderTemplateSection(PRODUCT_DECISIONS_SECTION),
|
|
2087
|
+
...renderTemplateSection(
|
|
2088
|
+
sectionSpec(
|
|
2089
|
+
"## Engineering Realization Links",
|
|
2090
|
+
[
|
|
2091
|
+
"List engineering truth that realizes this product truth; author canonical realized_by links in route YAML, not doc frontmatter.",
|
|
2092
|
+
"Do not summarize those engineering docs."
|
|
2093
|
+
],
|
|
2094
|
+
"engineering_realization_links"
|
|
2095
|
+
)
|
|
2096
|
+
),
|
|
2097
|
+
...includeNonGoals ? renderTemplateSection(NON_GOALS_SECTION) : [],
|
|
2098
|
+
...renderTemplateSection(SOURCE_REFERENCES_SECTION)
|
|
2220
2099
|
].join("\n");
|
|
2221
2100
|
};
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2101
|
+
var renderProductCapabilityDocTemplateFile = () => {
|
|
2102
|
+
return renderProductTruthDocTemplate(
|
|
2103
|
+
"product-capability",
|
|
2104
|
+
"{{title}}",
|
|
2105
|
+
[
|
|
2106
|
+
sectionSpec("## Capability Promise", [
|
|
2107
|
+
"State the single user-visible capability and what must be true for users or stakeholders.",
|
|
2108
|
+
"Do not describe implementation mechanics here."
|
|
2109
|
+
]),
|
|
2110
|
+
sectionSpec("## Users And Value", [
|
|
2111
|
+
"Describe who benefits from the capability and the durable value it protects.",
|
|
2112
|
+
"Tie claims to repository evidence, explicit user instruction, or current behavior."
|
|
2113
|
+
]),
|
|
2114
|
+
sectionSpec("## Capability Scope", [
|
|
2115
|
+
"Define what this capability includes and excludes, including product boundary constraints and adjacent systems.",
|
|
2116
|
+
"Capture important scope limits, ownership boundaries, and non-goal pointers here; keep technical contracts in engineering truth."
|
|
2117
|
+
]),
|
|
2118
|
+
sectionSpec("## Current Product Behavior", [
|
|
2119
|
+
"Describe current implemented user-visible behavior in present tense.",
|
|
2120
|
+
"Code files may appear in Source References when they directly prove current behavior."
|
|
2121
|
+
]),
|
|
2122
|
+
sectionSpec("## Acceptance Criteria", [
|
|
2123
|
+
"List observable criteria that show the capability promise is currently satisfied.",
|
|
2124
|
+
"Include criteria that review whether the capability stays within its stated scope and boundary.",
|
|
2125
|
+
"Use criteria that can be reviewed from repository evidence or explicit product instruction."
|
|
2126
|
+
])
|
|
2127
|
+
],
|
|
2128
|
+
true
|
|
2129
|
+
);
|
|
2233
2130
|
};
|
|
2234
|
-
var
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
"
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
"
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
"
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2131
|
+
var renderContractDocTemplateFile = () => {
|
|
2132
|
+
return renderTypedTruthDocTemplate("engineering-contract", "{{title}}", [
|
|
2133
|
+
sectionSpec("## Contract Surface", [
|
|
2134
|
+
"Identify the owned API, CLI, file format, event, protocol, permission boundary, or integration surface.",
|
|
2135
|
+
"State consumers/producers, stability level, and the source files/tests that define the contract."
|
|
2136
|
+
]),
|
|
2137
|
+
sectionSpec("## Inputs", [
|
|
2138
|
+
"Document accepted parameters, payloads, files, environment/config keys, permissions, and validation rules.",
|
|
2139
|
+
"Include required/optional status, defaults, constraints, and normalization behavior."
|
|
2140
|
+
]),
|
|
2141
|
+
sectionSpec("## Outputs", [
|
|
2142
|
+
"Document returned values, emitted files/events, state changes, side effects, and success diagnostics.",
|
|
2143
|
+
"Make externally observable behavior explicit enough for compatibility review."
|
|
2144
|
+
]),
|
|
2145
|
+
sectionSpec("## Errors And Diagnostics", [
|
|
2146
|
+
"List error classes, exit/status codes, user-facing diagnostics, retries, and recoverability expectations.",
|
|
2147
|
+
"Distinguish validation errors, dependency failures, authorization failures, and internal faults when applicable."
|
|
2148
|
+
]),
|
|
2149
|
+
sectionSpec("## Compatibility Rules", [
|
|
2150
|
+
"State backward/forward compatibility guarantees, tolerated inputs, deprecation rules, and breaking-change triggers.",
|
|
2151
|
+
"Include compatibility tests or review questions that protect the contract."
|
|
2152
|
+
]),
|
|
2153
|
+
sectionSpec("## Versioning And Migration", [
|
|
2154
|
+
"Document version negotiation, schema/API version fields, rollout requirements, migration steps, and rollback expectations.",
|
|
2155
|
+
"State 'Not versioned' only when the implementation truly has no versioning or migration surface."
|
|
2156
|
+
])
|
|
2157
|
+
]);
|
|
2253
2158
|
};
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2159
|
+
var renderArchitectureDocTemplateFile = () => {
|
|
2160
|
+
return renderTypedTruthDocTemplate("engineering-architecture", "{{title}}", [
|
|
2161
|
+
sectionSpec("## System Role", [
|
|
2162
|
+
"Describe the current architectural role of this subsystem/component in the larger system.",
|
|
2163
|
+
"State the primary responsibilities, consumers, providers, and why this boundary exists now."
|
|
2164
|
+
]),
|
|
2165
|
+
sectionSpec("## Boundaries", [
|
|
2166
|
+
"Define owned code/config/data, external dependencies, trust boundaries, and interfaces crossed by this architecture.",
|
|
2167
|
+
"Name what is deliberately outside the boundary and link neighboring architecture or contract docs when they own it."
|
|
2168
|
+
]),
|
|
2169
|
+
sectionSpec("## Components", [
|
|
2170
|
+
"List the major runtime/build-time components, modules, services, jobs, or generated artifacts and their responsibilities.",
|
|
2171
|
+
"Keep the component list current and evidence-backed; avoid speculative target architecture."
|
|
2172
|
+
]),
|
|
2173
|
+
sectionSpec("## Data And Control Flow", [
|
|
2174
|
+
"Describe important data movement, command/control paths, synchronization points, state ownership, and failure paths.",
|
|
2175
|
+
"Call out persistence, queues, caches, external calls, and security-sensitive transitions where relevant."
|
|
2176
|
+
]),
|
|
2177
|
+
sectionSpec("## Ownership", [
|
|
2178
|
+
"Document team/module ownership, review responsibility, operational responsibility, and escalation paths if known.",
|
|
2179
|
+
"If ownership is inferred from codeowners, config, or repository structure, cite that evidence."
|
|
2180
|
+
]),
|
|
2181
|
+
sectionSpec("## Cross-Cutting Constraints", [
|
|
2182
|
+
"Record active constraints such as security, privacy, reliability, performance, portability, maintainability, compliance, and cost.",
|
|
2183
|
+
"Tie constraints to source evidence, tests, standards, or operational requirements where available."
|
|
2184
|
+
])
|
|
2185
|
+
]);
|
|
2267
2186
|
};
|
|
2268
|
-
var
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2187
|
+
var renderWorkflowDocTemplateFile = () => {
|
|
2188
|
+
return renderTypedTruthDocTemplate("engineering-workflow", "{{title}}", [
|
|
2189
|
+
sectionSpec("## Triggers", [
|
|
2190
|
+
"List events, commands, schedules, user actions, webhooks, or dependency signals that start this workflow.",
|
|
2191
|
+
"Include preconditions, authorization requirements, debounce/coalescing behavior, and disabled states when applicable."
|
|
2192
|
+
]),
|
|
2193
|
+
sectionSpec("## Inputs", [
|
|
2194
|
+
"Document data, files, config, context, credentials, and environmental assumptions consumed by the workflow.",
|
|
2195
|
+
"Include validation, defaults, and normalization that happen before execution."
|
|
2196
|
+
]),
|
|
2197
|
+
sectionSpec("## Execution Model", [
|
|
2198
|
+
"Describe synchronous/asynchronous execution, concurrency, locking, leases, batching, ordering, and idempotency behavior.",
|
|
2199
|
+
"State whether the workflow waits for user action, runs in the background, is distributed, or is delegated to another system."
|
|
2200
|
+
]),
|
|
2201
|
+
sectionSpec("## Steps", [
|
|
2202
|
+
"Capture the current ordered steps or phases at a level useful for maintenance and review.",
|
|
2203
|
+
"Reference implementation entrypoints instead of duplicating line-by-line code behavior."
|
|
2204
|
+
]),
|
|
2205
|
+
sectionSpec("## State, Retry, And Failure Behavior", [
|
|
2206
|
+
"Document state transitions, retries, timeouts, compensation, fallback, partial-success, and terminal-failure behavior.",
|
|
2207
|
+
"Make externally visible failure semantics and recovery responsibilities clear."
|
|
2208
|
+
]),
|
|
2209
|
+
sectionSpec("## Outputs", [
|
|
2210
|
+
"List artifacts, state changes, notifications, logs, metrics, diagnostics, and downstream triggers produced by the workflow.",
|
|
2211
|
+
"Include success criteria and handoff points to other truth docs or systems."
|
|
2212
|
+
])
|
|
2213
|
+
]);
|
|
2287
2214
|
};
|
|
2288
|
-
var
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2215
|
+
var renderOperationsDocTemplateFile = () => {
|
|
2216
|
+
return renderTypedTruthDocTemplate("engineering-operations", "{{title}}", [
|
|
2217
|
+
sectionSpec("## Operational Surface", [
|
|
2218
|
+
"Describe what operators, maintainers, or automated systems can observe or control for this surface.",
|
|
2219
|
+
"Include commands, dashboards, alerts, runbooks, jobs, or operational APIs that define current operations."
|
|
2220
|
+
]),
|
|
2221
|
+
sectionSpec("## Runtime Topology", [
|
|
2222
|
+
"Document services, processes, containers, hosts, regions, dependencies, queues, stores, and network boundaries involved at runtime.",
|
|
2223
|
+
"State single-node/local behavior explicitly when there is no distributed topology."
|
|
2224
|
+
]),
|
|
2225
|
+
sectionSpec("## Configuration", [
|
|
2226
|
+
"List operational config, environment variables, feature flags, secrets references, defaults, and reload/restart requirements.",
|
|
2227
|
+
"Do not include secret values; describe storage and rotation expectations instead."
|
|
2228
|
+
]),
|
|
2229
|
+
sectionSpec("## Permissions", [
|
|
2230
|
+
"Document required identities, roles, scopes, filesystem/network permissions, and least-privilege boundaries.",
|
|
2231
|
+
"Include user-facing authorization behavior and operator access requirements when relevant."
|
|
2232
|
+
]),
|
|
2233
|
+
sectionSpec("## Deployment And Rollback", [
|
|
2234
|
+
"Describe deployment mechanism, migration ordering, compatibility windows, rollback path, and known irreversible operations.",
|
|
2235
|
+
"Call out manual review points, smoke checks, and post-deploy verification responsibilities."
|
|
2236
|
+
]),
|
|
2237
|
+
sectionSpec("## Availability And Observability", [
|
|
2238
|
+
"Capture availability expectations, health checks, metrics, logs, traces, alerts, SLO/error-budget signals, and known blind spots.",
|
|
2239
|
+
"Include what maintainers should inspect first during incidents or degraded behavior."
|
|
2240
|
+
])
|
|
2241
|
+
]);
|
|
2294
2242
|
};
|
|
2295
|
-
var
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
${block}
|
|
2324
|
-
|
|
2325
|
-
${after}`;
|
|
2243
|
+
var renderTestBehaviorDocTemplateFile = () => {
|
|
2244
|
+
return renderTypedTruthDocTemplate("engineering-test-behavior", "{{title}}", [
|
|
2245
|
+
sectionSpec("## Test Surface", [
|
|
2246
|
+
"Define the behavior, contract, architecture, or workflow surface these tests verify.",
|
|
2247
|
+
"Link the canonical truth docs and code paths the tests are meant to protect."
|
|
2248
|
+
]),
|
|
2249
|
+
sectionSpec("## Fixtures And Data Model", [
|
|
2250
|
+
"Document fixtures, factories, seeds, mocks/fakes, test repositories, external-service substitutes, and data lifecycle rules.",
|
|
2251
|
+
"Include cleanup, determinism, privacy, and cross-test contamination constraints."
|
|
2252
|
+
]),
|
|
2253
|
+
sectionSpec("## Execution Model", [
|
|
2254
|
+
"Describe how tests run: command, framework, parallelism, isolation, network/filesystem assumptions, and required services.",
|
|
2255
|
+
"State whether tests are unit, integration, e2e, contract, smoke, regression, or generated checks."
|
|
2256
|
+
]),
|
|
2257
|
+
sectionSpec("## Assertions And Invariants", [
|
|
2258
|
+
"List the critical assertions, invariants, failure modes, and negative cases that make the tests meaningful.",
|
|
2259
|
+
"Tie assertions to product/contract rules rather than incidental implementation details."
|
|
2260
|
+
]),
|
|
2261
|
+
sectionSpec("## Isolation Rules", [
|
|
2262
|
+
"Document transaction boundaries, temp directories, fake clocks, network blocking, shared resources, and teardown rules.",
|
|
2263
|
+
"Call out known order dependencies or flake risks and how they are controlled."
|
|
2264
|
+
]),
|
|
2265
|
+
sectionSpec("## Reporting And Failure Semantics", [
|
|
2266
|
+
"Describe diagnostics, snapshots, logs, coverage signals, retry policy, and how maintainers should interpret failures.",
|
|
2267
|
+
"Include escalation or quarantine criteria for flaky or environment-sensitive tests."
|
|
2268
|
+
])
|
|
2269
|
+
]);
|
|
2326
2270
|
};
|
|
2327
2271
|
|
|
2328
2272
|
// src/init/hierarchy.ts
|
|
2329
|
-
import fs5 from "fs/promises";
|
|
2330
2273
|
var truthRoot2 = resolveTruthDocsRoot;
|
|
2331
2274
|
var BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-behavior.md";
|
|
2332
2275
|
var CONTRACT_DOC_TEMPLATE_FILE_NAME = "engineering-contract.md";
|
|
@@ -2336,7 +2279,7 @@ var OPERATIONS_DOC_TEMPLATE_FILE_NAME = "engineering-operations.md";
|
|
|
2336
2279
|
var TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-test-behavior.md";
|
|
2337
2280
|
var PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME = "product-capability.md";
|
|
2338
2281
|
var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePath) => {
|
|
2339
|
-
const rootIndexSource = await
|
|
2282
|
+
const rootIndexSource = await fs4.readFile(
|
|
2340
2283
|
resolveRepoPath(rootDir, rootIndexPath),
|
|
2341
2284
|
"utf8"
|
|
2342
2285
|
);
|
|
@@ -2357,7 +2300,7 @@ var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTempla
|
|
|
2357
2300
|
if (seededResult.status !== "unchanged") {
|
|
2358
2301
|
return seededResult;
|
|
2359
2302
|
}
|
|
2360
|
-
const existingTemplate = await
|
|
2303
|
+
const existingTemplate = await fs4.readFile(
|
|
2361
2304
|
resolveRepoPath(rootDir, templatePath),
|
|
2362
2305
|
"utf8"
|
|
2363
2306
|
);
|
|
@@ -4974,11 +4917,11 @@ var renderGeneratedSurfaceCatalog = (config) => {
|
|
|
4974
4917
|
};
|
|
4975
4918
|
|
|
4976
4919
|
// src/init/lifecycle.ts
|
|
4977
|
-
import
|
|
4920
|
+
import fs5 from "fs/promises";
|
|
4978
4921
|
var plannedContents = /* @__PURE__ */ new WeakMap();
|
|
4979
4922
|
var readFile = async (rootDir, filePath) => {
|
|
4980
4923
|
try {
|
|
4981
|
-
return await
|
|
4924
|
+
return await fs5.readFile(resolveRepoPath(rootDir, filePath), "utf8");
|
|
4982
4925
|
} catch (error) {
|
|
4983
4926
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
4984
4927
|
return null;
|
|
@@ -4991,7 +4934,7 @@ var listFiles = async (rootDir, directory) => {
|
|
|
4991
4934
|
while (stack.length > 0) {
|
|
4992
4935
|
const current = stack.pop();
|
|
4993
4936
|
try {
|
|
4994
|
-
for (const entry of await
|
|
4937
|
+
for (const entry of await fs5.readdir(resolveRepoPath(rootDir, current), {
|
|
4995
4938
|
withFileTypes: true
|
|
4996
4939
|
})) {
|
|
4997
4940
|
const next = `${current}/${entry.name}`;
|
|
@@ -5009,7 +4952,7 @@ var findRetiredSurfaces = async (rootDir) => {
|
|
|
5009
4952
|
const paths = /* @__PURE__ */ new Set();
|
|
5010
4953
|
for (const filePath of RETIRED_GENERATED_SURFACES.exactPaths) {
|
|
5011
4954
|
try {
|
|
5012
|
-
await
|
|
4955
|
+
await fs5.lstat(resolveRepoPath(rootDir, filePath));
|
|
5013
4956
|
paths.add(filePath);
|
|
5014
4957
|
} catch (error) {
|
|
5015
4958
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
@@ -5027,7 +4970,7 @@ var findRetiredSurfaces = async (rootDir) => {
|
|
|
5027
4970
|
paths.add(filePath);
|
|
5028
4971
|
let packages = [];
|
|
5029
4972
|
try {
|
|
5030
|
-
packages = (await
|
|
4973
|
+
packages = (await fs5.readdir(resolveRepoPath(rootDir, skillRoot), {
|
|
5031
4974
|
withFileTypes: true
|
|
5032
4975
|
})).filter(
|
|
5033
4976
|
(entry) => entry.isDirectory() && entry.name.startsWith("truthmark-")
|
|
@@ -5040,7 +4983,7 @@ var findRetiredSurfaces = async (rootDir) => {
|
|
|
5040
4983
|
for (const retiredFile of RETIRED_GENERATED_SURFACES.retiredPackageFiles) {
|
|
5041
4984
|
const filePath = `${skillRoot}/${packageName}/${retiredFile}`;
|
|
5042
4985
|
try {
|
|
5043
|
-
await
|
|
4986
|
+
await fs5.lstat(resolveRepoPath(rootDir, filePath));
|
|
5044
4987
|
paths.add(filePath);
|
|
5045
4988
|
} catch (error) {
|
|
5046
4989
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
@@ -5209,23 +5152,23 @@ var applyLifecyclePlan = async (rootDir, plan) => {
|
|
|
5209
5152
|
for (const entry of plan.entries) {
|
|
5210
5153
|
const absolutePath = resolveRepoPath(rootDir, entry.path);
|
|
5211
5154
|
if (entry.action === "remove-file") {
|
|
5212
|
-
const stat = await
|
|
5155
|
+
const stat = await fs5.lstat(absolutePath);
|
|
5213
5156
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1)
|
|
5214
5157
|
throw new Error(
|
|
5215
5158
|
`Refusing unsafe generated file removal: ${entry.path}`
|
|
5216
5159
|
);
|
|
5217
|
-
await
|
|
5160
|
+
await fs5.rm(absolutePath);
|
|
5218
5161
|
} else if (entry.action === "remove-managed-block") {
|
|
5219
|
-
const stat = await
|
|
5162
|
+
const stat = await fs5.lstat(absolutePath);
|
|
5220
5163
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1)
|
|
5221
5164
|
throw new Error(`Refusing unsafe managed-block removal: ${entry.path}`);
|
|
5222
|
-
const content = await
|
|
5165
|
+
const content = await fs5.readFile(absolutePath, "utf8");
|
|
5223
5166
|
const block = parseManagedBlock(content);
|
|
5224
5167
|
if (block.status !== "valid")
|
|
5225
5168
|
throw new Error(`Managed block changed during removal: ${entry.path}`);
|
|
5226
5169
|
const remaining = `${content.slice(0, block.start)}${content.slice(block.end)}`;
|
|
5227
|
-
if (remaining.trim().length === 0) await
|
|
5228
|
-
else await
|
|
5170
|
+
if (remaining.trim().length === 0) await fs5.rm(absolutePath);
|
|
5171
|
+
else await fs5.writeFile(absolutePath, remaining, "utf8");
|
|
5229
5172
|
}
|
|
5230
5173
|
}
|
|
5231
5174
|
return { ...plan, applied: true };
|
|
@@ -5235,7 +5178,7 @@ var applyLifecyclePlan = async (rootDir, plan) => {
|
|
|
5235
5178
|
var writeManagedAgentsFile = async (rootDir, path13 = "AGENTS.md", block) => {
|
|
5236
5179
|
let existingContent = null;
|
|
5237
5180
|
try {
|
|
5238
|
-
existingContent = await
|
|
5181
|
+
existingContent = await fs6.readFile(resolveRepoPath(rootDir, path13), "utf8");
|
|
5239
5182
|
} catch (error) {
|
|
5240
5183
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
5241
5184
|
throw error;
|
|
@@ -5289,28 +5232,87 @@ var writeDiagnostics = (results, config) => {
|
|
|
5289
5232
|
file: result.path
|
|
5290
5233
|
}));
|
|
5291
5234
|
};
|
|
5292
|
-
var
|
|
5235
|
+
var normalizeRequestedPlatforms = (values) => {
|
|
5236
|
+
const unsupported = values.filter(
|
|
5237
|
+
(value) => !SUPPORTED_PLATFORMS.includes(value)
|
|
5238
|
+
);
|
|
5239
|
+
const selected = new Set(values);
|
|
5240
|
+
return {
|
|
5241
|
+
platforms: SUPPORTED_PLATFORMS.filter((platform) => selected.has(platform)),
|
|
5242
|
+
unsupported: [...new Set(unsupported)]
|
|
5243
|
+
};
|
|
5244
|
+
};
|
|
5245
|
+
var runInit = async (cwd, options = {}) => {
|
|
5293
5246
|
const repository = await getGitRepository(cwd);
|
|
5294
5247
|
const rootDir = repository.worktreePath;
|
|
5295
5248
|
const loadedConfig = await loadConfig(rootDir);
|
|
5296
|
-
|
|
5249
|
+
const repositoryData = {
|
|
5250
|
+
repositoryRoot: repository.repositoryRoot,
|
|
5251
|
+
worktreePath: repository.worktreePath,
|
|
5252
|
+
branchName: repository.branchName,
|
|
5253
|
+
isDetached: repository.isDetached,
|
|
5254
|
+
isUnborn: repository.isUnborn
|
|
5255
|
+
};
|
|
5256
|
+
if (loadedConfig.status === "invalid") {
|
|
5297
5257
|
return {
|
|
5298
5258
|
command: "init",
|
|
5299
|
-
summary: "Truthmark init
|
|
5259
|
+
summary: "Truthmark init made no changes because config is invalid.",
|
|
5300
5260
|
diagnostics: loadedConfig.diagnostics,
|
|
5301
|
-
data:
|
|
5302
|
-
repositoryRoot: repository.repositoryRoot,
|
|
5303
|
-
worktreePath: repository.worktreePath,
|
|
5304
|
-
branchName: repository.branchName,
|
|
5305
|
-
isDetached: repository.isDetached,
|
|
5306
|
-
isUnborn: repository.isUnborn
|
|
5307
|
-
}
|
|
5261
|
+
data: repositoryData
|
|
5308
5262
|
};
|
|
5309
5263
|
}
|
|
5264
|
+
const savedPlatforms = loadedConfig.config?.platforms ?? [];
|
|
5265
|
+
let requestedPlatforms;
|
|
5266
|
+
if (options.platforms !== void 0) requestedPlatforms = options.platforms;
|
|
5267
|
+
else if (options.selectPlatforms) {
|
|
5268
|
+
const selected = await options.selectPlatforms(savedPlatforms);
|
|
5269
|
+
if (selected === null)
|
|
5270
|
+
return {
|
|
5271
|
+
command: "init",
|
|
5272
|
+
summary: "Truthmark init cancelled; no repository files were changed.",
|
|
5273
|
+
diagnostics: [],
|
|
5274
|
+
data: { ...repositoryData, cancelled: true }
|
|
5275
|
+
};
|
|
5276
|
+
requestedPlatforms = selected;
|
|
5277
|
+
} else requestedPlatforms = savedPlatforms;
|
|
5278
|
+
const normalized = normalizeRequestedPlatforms(requestedPlatforms);
|
|
5279
|
+
if (normalized.unsupported.length > 0)
|
|
5280
|
+
return {
|
|
5281
|
+
command: "init",
|
|
5282
|
+
summary: "Truthmark init requires supported platform values.",
|
|
5283
|
+
diagnostics: normalized.unsupported.map((platform) => ({
|
|
5284
|
+
category: "config",
|
|
5285
|
+
severity: "error",
|
|
5286
|
+
message: `Unsupported Truthmark platform: ${platform}.`,
|
|
5287
|
+
file: ".truthmark/config.yml"
|
|
5288
|
+
})),
|
|
5289
|
+
data: repositoryData
|
|
5290
|
+
};
|
|
5291
|
+
const config = {
|
|
5292
|
+
...loadedConfig.config ?? createDefaultConfig(),
|
|
5293
|
+
platforms: normalized.platforms
|
|
5294
|
+
};
|
|
5295
|
+
const existingConfigSource = loadedConfig.config ? await fs6.readFile(resolveRepoPath(rootDir, loadedConfig.configPath), "utf8") : null;
|
|
5296
|
+
const configSource = existingConfigSource ? updateConfigPlatforms(existingConfigSource, normalized.platforms) : renderConfig(normalized.platforms);
|
|
5297
|
+
const configDiagnostics = loadedConfig.status === "loaded" ? loadedConfig.diagnostics : [];
|
|
5310
5298
|
const results = [];
|
|
5311
|
-
const config = loadedConfig.config;
|
|
5312
5299
|
const block = renderAgentsBlock(config);
|
|
5313
5300
|
const platformFiles = renderGeneratedSurfaces(config, block);
|
|
5301
|
+
if (!await isSafeExactFile(rootDir, loadedConfig.configPath, true)) {
|
|
5302
|
+
return {
|
|
5303
|
+
command: "init",
|
|
5304
|
+
summary: "Truthmark init made no changes because the config path is unsafe.",
|
|
5305
|
+
diagnostics: [
|
|
5306
|
+
{
|
|
5307
|
+
category: "config",
|
|
5308
|
+
severity: "error",
|
|
5309
|
+
message: "Truthmark config path must be a regular file contained in the repository.",
|
|
5310
|
+
file: loadedConfig.configPath
|
|
5311
|
+
}
|
|
5312
|
+
],
|
|
5313
|
+
data: repositoryData
|
|
5314
|
+
};
|
|
5315
|
+
}
|
|
5314
5316
|
const lifecyclePlan = await buildLifecyclePlan(
|
|
5315
5317
|
rootDir,
|
|
5316
5318
|
config,
|
|
@@ -5321,26 +5323,29 @@ var runInit = async (cwd) => {
|
|
|
5321
5323
|
return {
|
|
5322
5324
|
command: "init",
|
|
5323
5325
|
summary: "Truthmark init made no changes because generated-surface preflight failed.",
|
|
5324
|
-
diagnostics: [...
|
|
5325
|
-
data: { lifecyclePlan }
|
|
5326
|
+
diagnostics: [...configDiagnostics, ...lifecyclePlan.diagnostics],
|
|
5327
|
+
data: { ...repositoryData, lifecyclePlan }
|
|
5326
5328
|
};
|
|
5327
5329
|
}
|
|
5330
|
+
results.push(...await scaffoldHierarchy(rootDir, config));
|
|
5331
|
+
for (const file of platformFiles) {
|
|
5332
|
+
results.push(await writePlatformFile(rootDir, file));
|
|
5333
|
+
}
|
|
5334
|
+
results.push(
|
|
5335
|
+
await writeRepoFile(rootDir, loadedConfig.configPath, configSource)
|
|
5336
|
+
);
|
|
5328
5337
|
const appliedLifecyclePlan = await applyLifecyclePlan(rootDir, lifecyclePlan);
|
|
5329
5338
|
if (!appliedLifecyclePlan.applicable) {
|
|
5330
5339
|
return {
|
|
5331
5340
|
command: "init",
|
|
5332
5341
|
summary: "Truthmark init made no changes because generated-surface preflight failed.",
|
|
5333
5342
|
diagnostics: [
|
|
5334
|
-
...
|
|
5343
|
+
...configDiagnostics,
|
|
5335
5344
|
...appliedLifecyclePlan.diagnostics
|
|
5336
5345
|
],
|
|
5337
|
-
data: { lifecyclePlan: appliedLifecyclePlan }
|
|
5346
|
+
data: { ...repositoryData, lifecyclePlan: appliedLifecyclePlan }
|
|
5338
5347
|
};
|
|
5339
5348
|
}
|
|
5340
|
-
results.push(...await scaffoldHierarchy(rootDir, config));
|
|
5341
|
-
for (const file of platformFiles) {
|
|
5342
|
-
results.push(await writePlatformFile(rootDir, file));
|
|
5343
|
-
}
|
|
5344
5349
|
const changedResults = results.filter(
|
|
5345
5350
|
(result) => result.status !== "unchanged"
|
|
5346
5351
|
);
|
|
@@ -5351,7 +5356,7 @@ var runInit = async (cwd) => {
|
|
|
5351
5356
|
command: "init",
|
|
5352
5357
|
summary: changedResults.length > 0 || lifecycleChanged ? "Initialized or updated the Truthmark repository scaffold." : "Truthmark repository scaffold is already up to date.",
|
|
5353
5358
|
diagnostics: [
|
|
5354
|
-
...
|
|
5359
|
+
...configDiagnostics,
|
|
5355
5360
|
...appliedLifecyclePlan.diagnostics,
|
|
5356
5361
|
...appliedLifecyclePlan.entries.map((entry) => ({
|
|
5357
5362
|
category: "generated-surface",
|
|
@@ -5362,16 +5367,68 @@ var runInit = async (cwd) => {
|
|
|
5362
5367
|
...writeDiagnostics(results, config)
|
|
5363
5368
|
],
|
|
5364
5369
|
data: {
|
|
5365
|
-
|
|
5366
|
-
worktreePath: repository.worktreePath,
|
|
5367
|
-
branchName: repository.branchName,
|
|
5368
|
-
isDetached: repository.isDetached,
|
|
5369
|
-
isUnborn: repository.isUnborn,
|
|
5370
|
+
...repositoryData,
|
|
5370
5371
|
lifecyclePlan: appliedLifecyclePlan
|
|
5371
5372
|
}
|
|
5372
5373
|
};
|
|
5373
5374
|
};
|
|
5374
5375
|
|
|
5376
|
+
// src/cli/platform-selection.ts
|
|
5377
|
+
import { createInterface } from "readline/promises";
|
|
5378
|
+
var PLATFORM_LABELS = {
|
|
5379
|
+
codex: "Codex",
|
|
5380
|
+
opencode: "OpenCode",
|
|
5381
|
+
"claude-code": "Claude Code",
|
|
5382
|
+
"github-copilot": "GitHub Copilot",
|
|
5383
|
+
antigravity: "Antigravity",
|
|
5384
|
+
cursor: "Cursor"
|
|
5385
|
+
};
|
|
5386
|
+
var normalizePlatforms2 = (platforms) => {
|
|
5387
|
+
const selected = new Set(platforms);
|
|
5388
|
+
return SUPPORTED_PLATFORMS.filter((platform) => selected.has(platform));
|
|
5389
|
+
};
|
|
5390
|
+
var renderPlatformChoices = (defaults) => {
|
|
5391
|
+
const selected = new Set(defaults);
|
|
5392
|
+
return SUPPORTED_PLATFORMS.map(
|
|
5393
|
+
(platform, index) => `${index + 1}. ${PLATFORM_LABELS[platform]} [${platform}]${selected.has(platform) ? " (selected)" : ""}`
|
|
5394
|
+
).join("\n");
|
|
5395
|
+
};
|
|
5396
|
+
var parsePlatformSelection = (input, defaults) => {
|
|
5397
|
+
const value = input.trim().toLowerCase();
|
|
5398
|
+
if (value === "") return normalizePlatforms2(defaults);
|
|
5399
|
+
if (value === "q" || value === "quit") return null;
|
|
5400
|
+
if (value === "none") return [];
|
|
5401
|
+
const selected = value.split(",").map((token) => {
|
|
5402
|
+
const index = Number(token.trim());
|
|
5403
|
+
if (!Number.isInteger(index) || index < 1 || index > SUPPORTED_PLATFORMS.length)
|
|
5404
|
+
throw new Error(`Unsupported platform choice: ${token.trim()}`);
|
|
5405
|
+
return SUPPORTED_PLATFORMS[index - 1];
|
|
5406
|
+
});
|
|
5407
|
+
return normalizePlatforms2(selected);
|
|
5408
|
+
};
|
|
5409
|
+
var promptForPlatforms = async (options) => {
|
|
5410
|
+
options.output.write(`${renderPlatformChoices(options.defaults)}
|
|
5411
|
+
`);
|
|
5412
|
+
const readline = createInterface({ input: options.input, output: options.output });
|
|
5413
|
+
try {
|
|
5414
|
+
for (; ; ) {
|
|
5415
|
+
const answer = await readline.question(
|
|
5416
|
+
"Select platforms by number (comma-separated), 'none' for CLI-only, or 'q' to cancel: "
|
|
5417
|
+
);
|
|
5418
|
+
try {
|
|
5419
|
+
return parsePlatformSelection(answer, options.defaults);
|
|
5420
|
+
} catch (error) {
|
|
5421
|
+
options.output.write(
|
|
5422
|
+
`${error instanceof Error ? error.message : String(error)}
|
|
5423
|
+
`
|
|
5424
|
+
);
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
} finally {
|
|
5428
|
+
readline.close();
|
|
5429
|
+
}
|
|
5430
|
+
};
|
|
5431
|
+
|
|
5375
5432
|
// src/init/uninstall.ts
|
|
5376
5433
|
var runUninstall = async (cwd, mode) => {
|
|
5377
5434
|
const repository = await getGitRepository(cwd);
|
|
@@ -5408,7 +5465,7 @@ var runUninstall = async (cwd, mode) => {
|
|
|
5408
5465
|
};
|
|
5409
5466
|
|
|
5410
5467
|
// src/checks/branch-scope.ts
|
|
5411
|
-
import
|
|
5468
|
+
import fs7 from "fs/promises";
|
|
5412
5469
|
import fg from "fast-glob";
|
|
5413
5470
|
|
|
5414
5471
|
// src/markdown/hash.ts
|
|
@@ -5464,7 +5521,7 @@ var getBranchScopeData = async (cwd) => {
|
|
|
5464
5521
|
}
|
|
5465
5522
|
for (const relativePath of [...relevantFiles].sort()) {
|
|
5466
5523
|
try {
|
|
5467
|
-
const source = await
|
|
5524
|
+
const source = await fs7.readFile(resolveWorktreePath(repository, relativePath), "utf8");
|
|
5468
5525
|
relevantFileHashes[relativePath] = hashText(source);
|
|
5469
5526
|
} catch (error) {
|
|
5470
5527
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -5481,14 +5538,14 @@ var getBranchScopeData = async (cwd) => {
|
|
|
5481
5538
|
};
|
|
5482
5539
|
|
|
5483
5540
|
// src/checks/authority.ts
|
|
5484
|
-
import
|
|
5541
|
+
import fs8 from "fs/promises";
|
|
5485
5542
|
import fg2 from "fast-glob";
|
|
5486
5543
|
var looksLikeGlob = (pattern) => {
|
|
5487
5544
|
return /[*?[\]{}()!+@]/u.test(pattern);
|
|
5488
5545
|
};
|
|
5489
5546
|
var pathExists = async (absolutePath) => {
|
|
5490
5547
|
try {
|
|
5491
|
-
await
|
|
5548
|
+
await fs8.stat(absolutePath);
|
|
5492
5549
|
return true;
|
|
5493
5550
|
} catch (error) {
|
|
5494
5551
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -5580,10 +5637,10 @@ var checkAuthority = async (rootDir, config) => {
|
|
|
5580
5637
|
};
|
|
5581
5638
|
|
|
5582
5639
|
// src/checks/frontmatter.ts
|
|
5583
|
-
import
|
|
5640
|
+
import fs9 from "fs/promises";
|
|
5584
5641
|
|
|
5585
5642
|
// src/markdown/frontmatter.ts
|
|
5586
|
-
import { parse as
|
|
5643
|
+
import { parse as parse4 } from "yaml";
|
|
5587
5644
|
var openingDelimiterPattern = /^(?:\uFEFF)?---[ \t]*(?:\r?\n|$)/u;
|
|
5588
5645
|
var closingDelimiterPattern = /^---[ \t]*(?:\r?\n|$)/mu;
|
|
5589
5646
|
var asRecord = (value) => {
|
|
@@ -5614,7 +5671,7 @@ var parseFrontmatter = (source, options = {}) => {
|
|
|
5614
5671
|
let data = {};
|
|
5615
5672
|
if (yamlSource.trim().length > 0) {
|
|
5616
5673
|
try {
|
|
5617
|
-
data = asRecord(
|
|
5674
|
+
data = asRecord(parse4(yamlSource));
|
|
5618
5675
|
} catch (error) {
|
|
5619
5676
|
if (options.throwOnInvalid) {
|
|
5620
5677
|
throw error;
|
|
@@ -5682,7 +5739,7 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
|
|
|
5682
5739
|
}
|
|
5683
5740
|
const absolutePath = resolveRepoPath(rootDir, markdownPath);
|
|
5684
5741
|
await assertRepoContainment(rootDir, absolutePath);
|
|
5685
|
-
const source = await
|
|
5742
|
+
const source = await fs9.readFile(absolutePath, "utf8");
|
|
5686
5743
|
let document;
|
|
5687
5744
|
try {
|
|
5688
5745
|
parseFrontmatter(source, { throwOnInvalid: true });
|
|
@@ -5772,11 +5829,11 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
|
|
|
5772
5829
|
};
|
|
5773
5830
|
|
|
5774
5831
|
// src/checks/links.ts
|
|
5775
|
-
import
|
|
5832
|
+
import fs10 from "fs/promises";
|
|
5776
5833
|
import path5 from "path";
|
|
5777
5834
|
var pathExists2 = async (absolutePath) => {
|
|
5778
5835
|
try {
|
|
5779
|
-
await
|
|
5836
|
+
await fs10.stat(absolutePath);
|
|
5780
5837
|
return true;
|
|
5781
5838
|
} catch (error) {
|
|
5782
5839
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -5792,7 +5849,7 @@ var checkLinks = async (rootDir, markdownPaths) => {
|
|
|
5792
5849
|
continue;
|
|
5793
5850
|
}
|
|
5794
5851
|
const absolutePath = resolveRepoPath(rootDir, markdownPath);
|
|
5795
|
-
const source = await
|
|
5852
|
+
const source = await fs10.readFile(absolutePath, "utf8");
|
|
5796
5853
|
let document;
|
|
5797
5854
|
try {
|
|
5798
5855
|
document = parseMarkdownDocument(source);
|
|
@@ -5834,12 +5891,12 @@ var checkLinks = async (rootDir, markdownPaths) => {
|
|
|
5834
5891
|
};
|
|
5835
5892
|
|
|
5836
5893
|
// src/checks/areas.ts
|
|
5837
|
-
import
|
|
5894
|
+
import fs13 from "fs/promises";
|
|
5838
5895
|
import fg5 from "fast-glob";
|
|
5839
5896
|
import micromatch5 from "micromatch";
|
|
5840
5897
|
|
|
5841
5898
|
// src/git/files.ts
|
|
5842
|
-
import
|
|
5899
|
+
import fs11 from "fs/promises";
|
|
5843
5900
|
import { execa as execa2 } from "execa";
|
|
5844
5901
|
import fg3 from "fast-glob";
|
|
5845
5902
|
import micromatch2 from "micromatch";
|
|
@@ -5876,7 +5933,7 @@ var isCurrentContainedFile = async (rootDir, relativePath) => {
|
|
|
5876
5933
|
try {
|
|
5877
5934
|
const absolutePath = resolveRepoPath(rootDir, relativePath);
|
|
5878
5935
|
await assertRepoContainment(rootDir, absolutePath);
|
|
5879
|
-
return (await
|
|
5936
|
+
return (await fs11.stat(absolutePath)).isFile();
|
|
5880
5937
|
} catch {
|
|
5881
5938
|
return false;
|
|
5882
5939
|
}
|
|
@@ -5901,7 +5958,7 @@ var discoverRepositoryFilePaths = async (rootDir, ignorePatterns) => {
|
|
|
5901
5958
|
};
|
|
5902
5959
|
|
|
5903
5960
|
// src/routing/area-resolver.ts
|
|
5904
|
-
import
|
|
5961
|
+
import fs12 from "fs/promises";
|
|
5905
5962
|
import fg4 from "fast-glob";
|
|
5906
5963
|
import micromatch3 from "micromatch";
|
|
5907
5964
|
var unique = (values) => {
|
|
@@ -5952,7 +6009,7 @@ var ensureChildPath = async (rootDir, areaFilesRoot, filePath) => {
|
|
|
5952
6009
|
var readRouteFile = async (rootDir, filePath) => {
|
|
5953
6010
|
try {
|
|
5954
6011
|
return {
|
|
5955
|
-
source: await
|
|
6012
|
+
source: await fs12.readFile(resolveRepoPath(rootDir, filePath), "utf8"),
|
|
5956
6013
|
diagnostic: null
|
|
5957
6014
|
};
|
|
5958
6015
|
} catch (error) {
|
|
@@ -6264,7 +6321,7 @@ var looksLikeGlob2 = (pattern) => {
|
|
|
6264
6321
|
};
|
|
6265
6322
|
var pathExists3 = async (absolutePath) => {
|
|
6266
6323
|
try {
|
|
6267
|
-
await
|
|
6324
|
+
await fs13.stat(absolutePath);
|
|
6268
6325
|
return true;
|
|
6269
6326
|
} catch (error) {
|
|
6270
6327
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -6667,7 +6724,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
6667
6724
|
};
|
|
6668
6725
|
|
|
6669
6726
|
// src/checks/decisions.ts
|
|
6670
|
-
import
|
|
6727
|
+
import fs14 from "fs/promises";
|
|
6671
6728
|
import micromatch6 from "micromatch";
|
|
6672
6729
|
var PRODUCT_CAPABILITY_REQUIRED_HEADINGS = [
|
|
6673
6730
|
"Capability Promise",
|
|
@@ -6783,7 +6840,7 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
|
|
|
6783
6840
|
(filePath) => truthDocumentMap.has(filePath) || isDecisionTruthCandidate(config, filePath)
|
|
6784
6841
|
).sort();
|
|
6785
6842
|
for (const filePath of candidatePaths) {
|
|
6786
|
-
const source = await
|
|
6843
|
+
const source = await fs14.readFile(
|
|
6787
6844
|
resolveRepoPath(rootDir, filePath),
|
|
6788
6845
|
"utf8"
|
|
6789
6846
|
);
|
|
@@ -6825,10 +6882,10 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
|
|
|
6825
6882
|
};
|
|
6826
6883
|
|
|
6827
6884
|
// src/checks/generated-surfaces.ts
|
|
6828
|
-
import
|
|
6885
|
+
import fs15 from "fs/promises";
|
|
6829
6886
|
var readOptionalFile = async (rootDir, filePath) => {
|
|
6830
6887
|
try {
|
|
6831
|
-
return await
|
|
6888
|
+
return await fs15.readFile(resolveRepoPath(rootDir, filePath), "utf8");
|
|
6832
6889
|
} catch (error) {
|
|
6833
6890
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
6834
6891
|
return null;
|
|
@@ -6898,7 +6955,7 @@ import path11 from "path";
|
|
|
6898
6955
|
import micromatch7 from "micromatch";
|
|
6899
6956
|
|
|
6900
6957
|
// src/repo-index/file-tree.ts
|
|
6901
|
-
import
|
|
6958
|
+
import fs16 from "fs/promises";
|
|
6902
6959
|
import path8 from "path";
|
|
6903
6960
|
|
|
6904
6961
|
// src/truth/source-references.ts
|
|
@@ -7043,7 +7100,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
7043
7100
|
for (const filePath of discoveredFiles) {
|
|
7044
7101
|
let stat;
|
|
7045
7102
|
try {
|
|
7046
|
-
stat = await
|
|
7103
|
+
stat = await fs16.stat(path8.join(rootDir, filePath));
|
|
7047
7104
|
} catch (error) {
|
|
7048
7105
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
7049
7106
|
continue;
|
|
@@ -7067,7 +7124,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
7067
7124
|
});
|
|
7068
7125
|
}
|
|
7069
7126
|
if (kind === "doc") {
|
|
7070
|
-
const source = await
|
|
7127
|
+
const source = await fs16.readFile(path8.join(rootDir, filePath), "utf8");
|
|
7071
7128
|
const parsed = parseFrontmatter(source);
|
|
7072
7129
|
const markdown = parseMarkdownDocument(parsed.content);
|
|
7073
7130
|
const title = markdown.headings.find((heading) => heading.depth === 1)?.text ?? null;
|
|
@@ -7095,7 +7152,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
7095
7152
|
};
|
|
7096
7153
|
|
|
7097
7154
|
// src/repo-index/package-metadata.ts
|
|
7098
|
-
import
|
|
7155
|
+
import fs17 from "fs/promises";
|
|
7099
7156
|
import path9 from "path";
|
|
7100
7157
|
import fg6 from "fast-glob";
|
|
7101
7158
|
var packageManagerFor = async (rootDir, packageDir) => {
|
|
@@ -7108,7 +7165,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
|
|
|
7108
7165
|
];
|
|
7109
7166
|
for (const [lockfile, manager] of lockfiles) {
|
|
7110
7167
|
try {
|
|
7111
|
-
await
|
|
7168
|
+
await fs17.access(path9.join(rootDir, packageDir, lockfile));
|
|
7112
7169
|
return manager;
|
|
7113
7170
|
} catch {
|
|
7114
7171
|
continue;
|
|
@@ -7126,7 +7183,7 @@ var discoverPackageMetadata = async (rootDir) => {
|
|
|
7126
7183
|
const packages = [];
|
|
7127
7184
|
for (const packageFile of packageFiles.sort()) {
|
|
7128
7185
|
const packageDir = path9.posix.dirname(packageFile) === "." ? "" : path9.posix.dirname(packageFile);
|
|
7129
|
-
const raw = JSON.parse(await
|
|
7186
|
+
const raw = JSON.parse(await fs17.readFile(path9.join(rootDir, packageFile), "utf8"));
|
|
7130
7187
|
const scripts = raw.scripts && typeof raw.scripts === "object" ? Object.keys(raw.scripts).sort() : [];
|
|
7131
7188
|
packages.push({
|
|
7132
7189
|
path: packageFile,
|
|
@@ -7231,7 +7288,7 @@ var buildRepoIndex = async (cwd) => {
|
|
|
7231
7288
|
import { execa as execa4 } from "execa";
|
|
7232
7289
|
|
|
7233
7290
|
// src/git/changes.ts
|
|
7234
|
-
import
|
|
7291
|
+
import fs18 from "fs/promises";
|
|
7235
7292
|
import path10 from "path";
|
|
7236
7293
|
import { execa as execa3 } from "execa";
|
|
7237
7294
|
var normalizePath3 = (filePath) => {
|
|
@@ -7243,7 +7300,7 @@ var listChangedPaths = async (cwd, args) => {
|
|
|
7243
7300
|
};
|
|
7244
7301
|
var pathExists4 = async (filePath) => {
|
|
7245
7302
|
try {
|
|
7246
|
-
await
|
|
7303
|
+
await fs18.access(filePath);
|
|
7247
7304
|
return true;
|
|
7248
7305
|
} catch {
|
|
7249
7306
|
return false;
|
|
@@ -7490,13 +7547,13 @@ var checkFreshness = async (rootDir, _config, _truthDocumentPaths, base) => {
|
|
|
7490
7547
|
};
|
|
7491
7548
|
|
|
7492
7549
|
// src/evidence/validate.ts
|
|
7493
|
-
import
|
|
7550
|
+
import fs20 from "fs/promises";
|
|
7494
7551
|
import fg7 from "fast-glob";
|
|
7495
7552
|
|
|
7496
7553
|
// src/evidence/parse.ts
|
|
7497
|
-
import
|
|
7554
|
+
import fs19 from "fs/promises";
|
|
7498
7555
|
import path12 from "path";
|
|
7499
|
-
import { parse as
|
|
7556
|
+
import { parse as parse5 } from "yaml";
|
|
7500
7557
|
var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
|
|
7501
7558
|
var topLevelEvidenceMarkerPattern = /^evidence\s*:/imu;
|
|
7502
7559
|
var toEvidenceReference = (truthDocPath, raw) => {
|
|
@@ -7514,7 +7571,7 @@ var toEvidenceReference = (truthDocPath, raw) => {
|
|
|
7514
7571
|
};
|
|
7515
7572
|
};
|
|
7516
7573
|
var parseEvidenceReferences = async (rootDir, truthDocPath) => {
|
|
7517
|
-
const source = await
|
|
7574
|
+
const source = await fs19.readFile(path12.join(rootDir, truthDocPath), "utf8");
|
|
7518
7575
|
const parsed = parseFrontmatter(source);
|
|
7519
7576
|
const references = [];
|
|
7520
7577
|
for (const entry of parseSourceReferences(source, truthDocPath)) {
|
|
@@ -7529,7 +7586,7 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
|
|
|
7529
7586
|
if (!topLevelEvidenceMarkerPattern.test(yamlBlock)) {
|
|
7530
7587
|
continue;
|
|
7531
7588
|
}
|
|
7532
|
-
const block =
|
|
7589
|
+
const block = parse5(yamlBlock);
|
|
7533
7590
|
const rawEvidence = block && typeof block === "object" && "evidence" in block ? block.evidence : null;
|
|
7534
7591
|
if (!Array.isArray(rawEvidence)) {
|
|
7535
7592
|
continue;
|
|
@@ -7547,7 +7604,7 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
|
|
|
7547
7604
|
// src/evidence/validate.ts
|
|
7548
7605
|
var pathExists5 = async (filePath) => {
|
|
7549
7606
|
try {
|
|
7550
|
-
await
|
|
7607
|
+
await fs20.access(filePath);
|
|
7551
7608
|
return true;
|
|
7552
7609
|
} catch {
|
|
7553
7610
|
return false;
|
|
@@ -7592,7 +7649,7 @@ var validateHash = async (rootDir, reference) => {
|
|
|
7592
7649
|
if (!reference.contentHash.startsWith("sha256:")) {
|
|
7593
7650
|
return diagnosticFor(reference, `Evidence hash for ${reference.path} must use sha256:.`);
|
|
7594
7651
|
}
|
|
7595
|
-
const source = await
|
|
7652
|
+
const source = await fs20.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
|
|
7596
7653
|
const lines = source.split("\n");
|
|
7597
7654
|
const startLine = reference.startLine ?? 1;
|
|
7598
7655
|
const endLine = reference.endLine ?? lines.length;
|
|
@@ -7606,7 +7663,7 @@ var validateLineSpan = async (rootDir, reference) => {
|
|
|
7606
7663
|
if (reference.startLine === void 0 && reference.endLine === void 0) {
|
|
7607
7664
|
return null;
|
|
7608
7665
|
}
|
|
7609
|
-
const source = await
|
|
7666
|
+
const source = await fs20.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
|
|
7610
7667
|
const lines = source.split("\n");
|
|
7611
7668
|
const startLine = reference.startLine ?? 1;
|
|
7612
7669
|
const endLine = reference.endLine ?? lines.length;
|
|
@@ -8207,10 +8264,14 @@ var buildWorkflowState = async (cwd, options) => {
|
|
|
8207
8264
|
comparisonBase ? { base: comparisonBase } : {}
|
|
8208
8265
|
);
|
|
8209
8266
|
const diagnostics = [
|
|
8210
|
-
...
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8267
|
+
...new Map(
|
|
8268
|
+
[
|
|
8269
|
+
...loadResult.diagnostics,
|
|
8270
|
+
...repoIndex.diagnostics,
|
|
8271
|
+
...impactSet?.diagnostics ?? [],
|
|
8272
|
+
...checkResult.diagnostics
|
|
8273
|
+
].map((diagnostic) => [JSON.stringify(diagnostic), diagnostic])
|
|
8274
|
+
).values()
|
|
8214
8275
|
];
|
|
8215
8276
|
const applicability = applicabilityFor(
|
|
8216
8277
|
options.workflow,
|
|
@@ -8251,7 +8312,7 @@ var buildWorkflowState = async (cwd, options) => {
|
|
|
8251
8312
|
};
|
|
8252
8313
|
|
|
8253
8314
|
// src/cli/handlers.ts
|
|
8254
|
-
import
|
|
8315
|
+
import fs21 from "fs/promises";
|
|
8255
8316
|
|
|
8256
8317
|
// src/agents/workflow-helper-validation.ts
|
|
8257
8318
|
import { parse as parseYaml2 } from "yaml";
|
|
@@ -8568,11 +8629,16 @@ var validateWriteLeaseText = (leaseText, changedText) => {
|
|
|
8568
8629
|
};
|
|
8569
8630
|
|
|
8570
8631
|
// src/cli/handlers.ts
|
|
8571
|
-
var
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8632
|
+
var runInit2 = async (options = {}) => {
|
|
8633
|
+
const interactive = options.platforms === void 0 && !options.json && process.stdin.isTTY && process.stdout.isTTY;
|
|
8634
|
+
return runInit(process.cwd(), {
|
|
8635
|
+
platforms: options.platforms,
|
|
8636
|
+
selectPlatforms: interactive ? (defaults) => promptForPlatforms({
|
|
8637
|
+
defaults,
|
|
8638
|
+
input: process.stdin,
|
|
8639
|
+
output: process.stdout
|
|
8640
|
+
}) : void 0
|
|
8641
|
+
});
|
|
8576
8642
|
};
|
|
8577
8643
|
var runUninstall2 = async (mode) => {
|
|
8578
8644
|
return runUninstall(process.cwd(), mode);
|
|
@@ -8645,9 +8711,22 @@ var invalidWorkflowResult = (command, workflow) => ({
|
|
|
8645
8711
|
request: workflow ? { workflow } : {}
|
|
8646
8712
|
}
|
|
8647
8713
|
});
|
|
8714
|
+
var summarizeValues = (values) => values.length <= 5 ? values.join(", ") : `${values.slice(0, 5).join(", ")} (+${values.length - 5} more)`;
|
|
8715
|
+
var renderWorkflowStatusSummary = (state) => {
|
|
8716
|
+
const card = state.workflowCard;
|
|
8717
|
+
return [
|
|
8718
|
+
`Truthmark workflow status completed for ${state.workflow}.`,
|
|
8719
|
+
`Applicability: ${state.applicability.state}.`,
|
|
8720
|
+
...card.affectedFiles.length > 0 ? [`Affected files: ${summarizeValues(card.affectedFiles)}`] : [],
|
|
8721
|
+
...card.likelyRouteOwners.length > 0 ? [`Likely route owners: ${summarizeValues(card.likelyRouteOwners)}`] : [],
|
|
8722
|
+
...card.suggestedTruthDocs.length > 0 ? [`Suggested truth docs: ${summarizeValues(card.suggestedTruthDocs)}`] : [],
|
|
8723
|
+
...card.openQuestions.length > 0 ? [`Open questions: ${summarizeValues(card.openQuestions)}`] : [],
|
|
8724
|
+
...state.nextSteps.length > 0 ? [`Next steps: ${summarizeValues(state.nextSteps)}`] : []
|
|
8725
|
+
].join("\n");
|
|
8726
|
+
};
|
|
8648
8727
|
var readHelperFile = async (filePath, helper) => {
|
|
8649
8728
|
try {
|
|
8650
|
-
return await
|
|
8729
|
+
return await fs21.readFile(filePath, "utf8");
|
|
8651
8730
|
} catch (error) {
|
|
8652
8731
|
const message = error instanceof Error ? error.message : String(error);
|
|
8653
8732
|
return { ok: false, helper, errors: [`could not read file: ${message}`] };
|
|
@@ -8685,7 +8764,7 @@ var runWorkflowStatus = async (options) => {
|
|
|
8685
8764
|
});
|
|
8686
8765
|
return {
|
|
8687
8766
|
command: "workflow status",
|
|
8688
|
-
summary:
|
|
8767
|
+
summary: renderWorkflowStatusSummary(workflowState),
|
|
8689
8768
|
diagnostics: workflowState.diagnostics,
|
|
8690
8769
|
data: {
|
|
8691
8770
|
request: {
|
|
@@ -8740,27 +8819,36 @@ var writeValidationResult = (command, result, options) => {
|
|
|
8740
8819
|
var addJsonOption = (command) => {
|
|
8741
8820
|
return command.option("--json", "Render command output as JSON");
|
|
8742
8821
|
};
|
|
8822
|
+
var collectPlatform = (value, previous) => [
|
|
8823
|
+
...previous ?? [],
|
|
8824
|
+
value
|
|
8825
|
+
];
|
|
8743
8826
|
var buildProgram = () => {
|
|
8744
8827
|
const program = new Command();
|
|
8745
8828
|
program.name("truthmark").description(
|
|
8746
8829
|
"Git-native, branch-scoped truth workflow installer for local AI coding agents."
|
|
8747
8830
|
).showHelpAfterError();
|
|
8748
|
-
addJsonOption(
|
|
8749
|
-
program.command("config").description(
|
|
8750
|
-
"Create or render the Truthmark repository config before initialization."
|
|
8751
|
-
).option(
|
|
8752
|
-
"--stdout",
|
|
8753
|
-
"Render default config in the JSON data payload without writing"
|
|
8754
|
-
).option("--force", "Overwrite an existing .truthmark/config.yml")
|
|
8755
|
-
).action(async (options) => {
|
|
8756
|
-
writeResult(await runConfig2(options), options);
|
|
8757
|
-
});
|
|
8758
8831
|
addJsonOption(
|
|
8759
8832
|
program.command("init").description(
|
|
8760
8833
|
"Initialize Truthmark workflow files in the current repository."
|
|
8834
|
+
).option(
|
|
8835
|
+
"--platform <id>",
|
|
8836
|
+
"Select a repository agent platform; repeat for multiple platforms",
|
|
8837
|
+
collectPlatform
|
|
8838
|
+
).option(
|
|
8839
|
+
"--clear-platforms",
|
|
8840
|
+
"Remove all configured repository agent platforms"
|
|
8761
8841
|
)
|
|
8762
8842
|
).action(async (options) => {
|
|
8763
|
-
|
|
8843
|
+
if (options.clearPlatforms && options.platform !== void 0) {
|
|
8844
|
+
program.error("truthmark init cannot combine --clear-platforms and --platform");
|
|
8845
|
+
return;
|
|
8846
|
+
}
|
|
8847
|
+
const platforms = options.clearPlatforms ? [] : options.platform;
|
|
8848
|
+
writeResult(
|
|
8849
|
+
await runInit2({ json: options.json, platforms }),
|
|
8850
|
+
options
|
|
8851
|
+
);
|
|
8764
8852
|
});
|
|
8765
8853
|
addJsonOption(
|
|
8766
8854
|
program.command("uninstall").description(
|