mustflow 2.22.4 → 2.22.5
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 +9 -75
- package/dist/cli/commands/contract-lint.js +2 -2
- package/dist/cli/commands/dashboard.js +14 -6
- package/dist/cli/commands/help.js +8 -9
- package/dist/cli/commands/impact.js +2 -3
- package/dist/cli/commands/init.js +61 -5
- package/dist/cli/commands/update.js +2 -2
- package/dist/cli/commands/version-sources.js +2 -3
- package/dist/cli/i18n/en.js +2 -0
- package/dist/cli/i18n/es.js +2 -0
- package/dist/cli/i18n/fr.js +2 -0
- package/dist/cli/i18n/hi.js +2 -0
- package/dist/cli/i18n/ko.js +2 -0
- package/dist/cli/i18n/zh.js +2 -0
- package/dist/cli/lib/agent-context.js +6 -11
- package/dist/cli/lib/local-index/index.js +14 -11
- package/dist/cli/lib/mustflow-read.js +41 -0
- package/dist/cli/lib/project-root.js +1 -2
- package/dist/cli/lib/repo-map.js +65 -16
- package/dist/cli/lib/templates.js +124 -8
- package/dist/cli/lib/toml.js +6 -1
- package/dist/cli/lib/validation/constants.js +2 -0
- package/dist/cli/lib/validation/index.js +291 -22
- package/dist/cli/lib/validation/primitives.js +2 -2
- package/dist/cli/lib/validation/test-selection.js +2 -2
- package/dist/core/bounded-output.js +32 -7
- package/dist/core/check-issues.js +7 -1
- package/dist/core/command-contract-validation.js +28 -4
- package/dist/core/command-env.js +1 -1
- package/dist/core/config-loading.js +9 -3
- package/dist/core/contract-lint.js +2 -1
- package/dist/core/safe-filesystem.js +11 -4
- package/dist/core/skill-route-alignment.js +1 -0
- package/dist/core/skill-route-explanation.js +9 -3
- package/dist/core/test-selection.js +2 -3
- package/dist/core/verification-scheduler.js +7 -6
- package/dist/core/version-sources.js +2 -3
- package/package.json +1 -1
- package/schemas/commands.schema.json +1 -0
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +10 -6
- package/templates/default/locales/en/.mustflow/skills/routes.toml +2 -2
- package/templates/default/manifest.toml +1 -1
|
@@ -11,13 +11,15 @@ import { listFilesRecursive, toPosixPath } from '../filesystem.js';
|
|
|
11
11
|
import { readGitChangedFiles } from '../git-changes.js';
|
|
12
12
|
import { inspectManifestLock } from '../manifest-lock.js';
|
|
13
13
|
import { generateRepoMap } from '../repo-map.js';
|
|
14
|
-
import {
|
|
14
|
+
import { parseTomlText, readMustflowTomlFile } from '../toml.js';
|
|
15
|
+
import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFileResult, } from '../mustflow-read.js';
|
|
15
16
|
import { getContractModelDefinitions, validateCandidateContractModelConfig, } from '../../../core/contract-models.js';
|
|
16
17
|
import { VERSIONING_CONFIG_PATH, detectVersionSourcePaths, readDeclaredVersionSources, releaseVersioningIsEnabled, } from '../../../core/version-sources.js';
|
|
17
|
-
import { ALLOWED_APPROVAL_ACTIONS, ALLOWED_APPROVAL_GATES, ALLOWED_BUDGET_LIMIT_ACTIONS, ALLOWED_CAPABILITY_STATES, ALLOWED_COMMIT_MESSAGE_STYLES, ALLOWED_COMPACTION_CATEGORIES, ALLOWED_COMPACTION_LONG_LIMIT_ACTIONS, ALLOWED_COMPACTION_RAW_LIMIT_ACTIONS, ALLOWED_COMPACTION_STATE_STORES, ALLOWED_COMPACTION_STRATEGIES, ALLOWED_CONTEXT_AUTHORITIES, ALLOWED_CONTEXT_DOCUMENT_AUTHORITIES, ALLOWED_CONTEXT_READ_POLICIES, ALLOWED_HANDOFF_MODES, ALLOWED_HARNESS_FRESH_CONTEXT_MODES, ALLOWED_HARNESS_MODES, ALLOWED_HARNESS_PHASES, ALLOWED_ISOLATION_PREFERENCES, ALLOWED_MAP_MODES, ALLOWED_MAP_PRIVACY_LEVELS, ALLOWED_PROJECT_PROFILES, ALLOWED_PROMPT_CACHE_STABLE_PREFIX_POLICIES, ALLOWED_PROMPT_CACHE_STRATEGIES, ALLOWED_PROMPT_CACHE_TASK_READ_POLICIES, ALLOWED_REFRESH_CHECKPOINTS, ALLOWED_REFRESH_METHODS, ALLOWED_REFRESH_MODES, ALLOWED_REFRESH_STATE_STORES, ALLOWED_SKILL_RESOURCE_TYPES, ALLOWED_SKILL_ROUTE_CATEGORIES, ALLOWED_SKILL_ROUTE_PROFILES, ALLOWED_SKILL_ROUTE_TYPES, ALLOWED_STALE_TEST_ACTIONS, ALLOWED_TEST_AUTHORING_POLICIES, ALLOWED_TEST_DELETION_REASONS, ALLOWED_TESTING_POLICIES, ALLOWED_TRANSLATION_POLICIES, ALLOWED_VERIFICATION_SELECTION_STRATEGIES, ALLOWED_VERSION_SOURCE_AUTHORITIES, ALLOWED_VERSION_SOURCE_KINDS, CAPABILITY_BOOLEAN_FIELDS, CAPABILITY_STATE_FIELDS, CONTEXT_AUTHORITY_DRIFT_PATTERNS, DESIGN_TOKEN_DEFINITION_PATTERNS, FORBIDDEN_RELEASE_VERSIONING_CONTRACT_FIELDS, FORBIDDEN_TEST_DELETION_REASONS, FORBIDDEN_VERIFICATION_SELECTION_AUTHORITY_FIELDS, LOCAL_ABSOLUTE_PATH_PATTERNS, LOCAL_TASK_STATE_ROOTS, RAW_COMMAND_FENCE_PATTERN, RELEASE_VERSIONING_BOOLEAN_FIELDS, REPO_MAP_DOC_ID, REPO_MAP_GENERATOR, REPO_MAP_LIFECYCLE, REPO_MAP_PRIVACY_MODE, REPO_MAP_RELATIVE_ROOT, REPO_MAP_REMOTE_OR_BRANCH_PATTERNS, REPO_MAP_SOURCE_FINGERPRINT_PATTERN, REPO_MAP_SOURCE_POLICY, REQUIRED_AGENT_LOOP_PHASES, REQUIRED_SKILL_SCRIPT_RUN_POLICY, REQUIRED_SKILL_SECTION_IDS, ROUTER_INDEX_FILES, ROUTER_INDEX_PROCEDURE_SECTION_PATTERN, SECRET_LIKE_CONTEXT_PATTERNS, SKILL_COMMAND_PERMISSION_CLAIM_PATTERNS, SKILL_INDEX_PATH, SKILL_PACK_ID_PATTERN, SKILL_RESOURCE_MANIFEST, SKILL_RESOURCE_ROOTS, SKILL_RESOURCE_TYPE_BY_ROOT, SKILL_ROUTE_CATEGORY_LABELS, SKILL_ROUTES_METADATA_PATH, SKILL_SECTION_MARKER_PATTERN, SUPPORTED_SKILL_SCHEMA_VERSION, TEST_AUTHORING_BOOLEAN_FIELDS, VERIFICATION_SELECTION_BOOLEAN_FIELDS, VOLATILE_REPO_MAP_PATTERNS } from './constants.js';
|
|
18
|
+
import { ALLOWED_APPROVAL_ACTIONS, ALLOWED_APPROVAL_GATES, ALLOWED_BUDGET_LIMIT_ACTIONS, ALLOWED_CAPABILITY_STATES, ALLOWED_COMMIT_MESSAGE_STYLES, ALLOWED_COMPACTION_CATEGORIES, ALLOWED_COMPACTION_LONG_LIMIT_ACTIONS, ALLOWED_COMPACTION_RAW_LIMIT_ACTIONS, ALLOWED_COMPACTION_STATE_STORES, ALLOWED_COMPACTION_STRATEGIES, ALLOWED_CONTEXT_AUTHORITIES, ALLOWED_CONTEXT_DOCUMENT_AUTHORITIES, ALLOWED_CONTEXT_READ_POLICIES, ALLOWED_HANDOFF_MODES, ALLOWED_HARNESS_FRESH_CONTEXT_MODES, ALLOWED_HARNESS_MODES, ALLOWED_HARNESS_PHASES, ALLOWED_ISOLATION_PREFERENCES, ALLOWED_MAP_MODES, ALLOWED_MAP_PRIVACY_LEVELS, ALLOWED_PROJECT_PROFILES, ALLOWED_REPO_MAP_DEGRADED_VALUES, ALLOWED_REPO_MAP_GIT_LS_FILES_STATUSES, ALLOWED_PROMPT_CACHE_STABLE_PREFIX_POLICIES, ALLOWED_PROMPT_CACHE_STRATEGIES, ALLOWED_PROMPT_CACHE_TASK_READ_POLICIES, ALLOWED_REFRESH_CHECKPOINTS, ALLOWED_REFRESH_METHODS, ALLOWED_REFRESH_MODES, ALLOWED_REFRESH_STATE_STORES, ALLOWED_SKILL_RESOURCE_TYPES, ALLOWED_SKILL_ROUTE_CATEGORIES, ALLOWED_SKILL_ROUTE_PROFILES, ALLOWED_SKILL_ROUTE_TYPES, ALLOWED_STALE_TEST_ACTIONS, ALLOWED_TEST_AUTHORING_POLICIES, ALLOWED_TEST_DELETION_REASONS, ALLOWED_TESTING_POLICIES, ALLOWED_TRANSLATION_POLICIES, ALLOWED_VERIFICATION_SELECTION_STRATEGIES, ALLOWED_VERSION_SOURCE_AUTHORITIES, ALLOWED_VERSION_SOURCE_KINDS, CAPABILITY_BOOLEAN_FIELDS, CAPABILITY_STATE_FIELDS, CONTEXT_AUTHORITY_DRIFT_PATTERNS, DESIGN_TOKEN_DEFINITION_PATTERNS, FORBIDDEN_RELEASE_VERSIONING_CONTRACT_FIELDS, FORBIDDEN_TEST_DELETION_REASONS, FORBIDDEN_VERIFICATION_SELECTION_AUTHORITY_FIELDS, LOCAL_ABSOLUTE_PATH_PATTERNS, LOCAL_TASK_STATE_ROOTS, RAW_COMMAND_FENCE_PATTERN, RELEASE_VERSIONING_BOOLEAN_FIELDS, REPO_MAP_DOC_ID, REPO_MAP_GENERATOR, REPO_MAP_LIFECYCLE, REPO_MAP_PRIVACY_MODE, REPO_MAP_RELATIVE_ROOT, REPO_MAP_REMOTE_OR_BRANCH_PATTERNS, REPO_MAP_SOURCE_FINGERPRINT_PATTERN, REPO_MAP_SOURCE_POLICY, REQUIRED_AGENT_LOOP_PHASES, REQUIRED_SKILL_SCRIPT_RUN_POLICY, REQUIRED_SKILL_SECTION_IDS, ROUTER_INDEX_FILES, ROUTER_INDEX_PROCEDURE_SECTION_PATTERN, SECRET_LIKE_CONTEXT_PATTERNS, SKILL_COMMAND_PERMISSION_CLAIM_PATTERNS, SKILL_INDEX_PATH, SKILL_PACK_ID_PATTERN, SKILL_RESOURCE_MANIFEST, SKILL_RESOURCE_ROOTS, SKILL_RESOURCE_TYPE_BY_ROOT, SKILL_ROUTE_CATEGORY_LABELS, SKILL_ROUTES_METADATA_PATH, SKILL_SECTION_MARKER_PATTERN, SUPPORTED_SKILL_SCHEMA_VERSION, TEST_AUTHORING_BOOLEAN_FIELDS, VERIFICATION_SELECTION_BOOLEAN_FIELDS, VOLATILE_REPO_MAP_PATTERNS } from './constants.js';
|
|
18
19
|
import { hasOwn, isPositiveInteger, isSafeRelativePath, pushStrictIssue, pushStrictWarning, validateAllowedStringField, validateBooleanField, validateExactStringArrayField, validateNestedTable, validatePathArrayField, validatePathField, validatePositiveIntegerField, validateRequiredFiles, validateRequiredPathField, validateRequiredStringField, validateStringArrayField, validateStringArrayMembers, validateStringField, validateTable, validateToml, validateWorkspaceRoots } from './primitives.js';
|
|
19
20
|
import { isConfiguredCommandIntent, isDeclaredCommandIntent } from './command-intents.js';
|
|
20
21
|
import { validateStrictTestSelectionConfig } from './test-selection.js';
|
|
22
|
+
import { getDefaultTemplate, getTemplateFiles } from '../templates.js';
|
|
21
23
|
export { describeCheckIssues, getCheckIssueId, } from '../../../core/check-issues.js';
|
|
22
24
|
function validateMustflowConfig(mustflowToml, issues) {
|
|
23
25
|
if (!mustflowToml) {
|
|
@@ -454,13 +456,16 @@ function validateSkills(projectRoot, issues) {
|
|
|
454
456
|
const skillsRoot = path.join(projectRoot, '.mustflow', 'skills');
|
|
455
457
|
const skillFiles = listFilesRecursive(skillsRoot).filter((relativePath) => relativePath.endsWith('/SKILL.md'));
|
|
456
458
|
for (const relativePath of skillFiles) {
|
|
457
|
-
const
|
|
458
|
-
const content =
|
|
459
|
+
const normalizedPath = `.mustflow/skills/${toPosixPath(relativePath)}`;
|
|
460
|
+
const content = readStrictMustflowText(projectRoot, normalizedPath, issues);
|
|
461
|
+
if (content === undefined) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
459
464
|
const sectionIds = readSkillSectionIds(content);
|
|
460
465
|
const missingSectionIds = REQUIRED_SKILL_SECTION_IDS.filter((sectionId) => !sectionIds.has(sectionId));
|
|
461
466
|
if (missingSectionIds.length > 0) {
|
|
462
467
|
issues.push({
|
|
463
|
-
message: `Missing required skill section ids in
|
|
468
|
+
message: `Missing required skill section ids in ${normalizedPath}: ${missingSectionIds.join(', ')}`,
|
|
464
469
|
});
|
|
465
470
|
}
|
|
466
471
|
}
|
|
@@ -490,6 +495,16 @@ function parseSimpleFrontmatter(content) {
|
|
|
490
495
|
}
|
|
491
496
|
return frontmatter;
|
|
492
497
|
}
|
|
498
|
+
function readStrictMustflowText(projectRoot, relativePath, issues, options = {}) {
|
|
499
|
+
const result = readMustflowTextFileResult(projectRoot, relativePath, options);
|
|
500
|
+
if (result.ok) {
|
|
501
|
+
return result.content;
|
|
502
|
+
}
|
|
503
|
+
if (result.exists && result.error) {
|
|
504
|
+
pushStrictIssue(issues, `${toPosixPath(relativePath)} could not be read safely: ${result.error}`);
|
|
505
|
+
}
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
493
508
|
function readFrontmatterLines(content) {
|
|
494
509
|
if (!content.startsWith('---')) {
|
|
495
510
|
return [];
|
|
@@ -540,7 +555,10 @@ function validateContextDocuments(projectRoot, issues) {
|
|
|
540
555
|
const contextFiles = listFilesRecursive(contextRoot).filter((relativePath) => relativePath.endsWith('.md'));
|
|
541
556
|
for (const relativePath of contextFiles) {
|
|
542
557
|
const normalizedPath = `.mustflow/context/${toPosixPath(relativePath)}`;
|
|
543
|
-
const content =
|
|
558
|
+
const content = readStrictMustflowText(projectRoot, normalizedPath, issues);
|
|
559
|
+
if (content === undefined) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
544
562
|
const frontmatter = parseSimpleFrontmatter(content);
|
|
545
563
|
if (frontmatter.kind !== 'mustflow-context') {
|
|
546
564
|
issues.push({ message: `${normalizedPath} frontmatter kind must be "mustflow-context"` });
|
|
@@ -630,7 +648,10 @@ function validateStrictRouterIndexes(projectRoot, issues) {
|
|
|
630
648
|
if (!existsSync(filePath)) {
|
|
631
649
|
continue;
|
|
632
650
|
}
|
|
633
|
-
const content =
|
|
651
|
+
const content = readStrictMustflowText(projectRoot, relativePath, issues);
|
|
652
|
+
if (content === undefined) {
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
634
655
|
if (ROUTER_INDEX_PROCEDURE_SECTION_PATTERN.test(content)) {
|
|
635
656
|
pushStrictIssue(issues, `${relativePath} must stay a routing index and must not embed skill procedure sections`);
|
|
636
657
|
}
|
|
@@ -724,7 +745,7 @@ function readSkillRouteMetadata(projectRoot, issues) {
|
|
|
724
745
|
}
|
|
725
746
|
let parsed;
|
|
726
747
|
try {
|
|
727
|
-
parsed =
|
|
748
|
+
parsed = readMustflowTomlFile(projectRoot, SKILL_ROUTES_METADATA_PATH);
|
|
728
749
|
}
|
|
729
750
|
catch (error) {
|
|
730
751
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -787,7 +808,10 @@ function validateSkillIndexRoutes(projectRoot, commandsToml, skillFiles, issues)
|
|
|
787
808
|
if (!existsSync(skillIndexPath)) {
|
|
788
809
|
return;
|
|
789
810
|
}
|
|
790
|
-
const skillIndexContent =
|
|
811
|
+
const skillIndexContent = readStrictMustflowText(projectRoot, SKILL_INDEX_PATH, issues);
|
|
812
|
+
if (skillIndexContent === undefined) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
791
815
|
validateSkillIndexRouteShape(skillIndexContent, issues);
|
|
792
816
|
const skillRoutes = parseSkillIndexRoutes(skillIndexContent);
|
|
793
817
|
const routedSkillPaths = new Set();
|
|
@@ -824,7 +848,11 @@ function validateSkillIndexRoutes(projectRoot, commandsToml, skillFiles, issues)
|
|
|
824
848
|
pushStrictIssue(issues, `${SKILL_INDEX_PATH} route ${route.skillPath} points to a missing skill document`);
|
|
825
849
|
continue;
|
|
826
850
|
}
|
|
827
|
-
const
|
|
851
|
+
const skillContent = readStrictMustflowText(projectRoot, route.skillPath, issues);
|
|
852
|
+
if (skillContent === undefined) {
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
const skillCommandIntents = new Set(readFrontmatterList(skillContent, 'command_intents'));
|
|
828
856
|
for (const intentName of route.commandIntents) {
|
|
829
857
|
if (!isDeclaredCommandIntent(commandsToml, intentName)) {
|
|
830
858
|
pushStrictIssue(issues, `${SKILL_INDEX_PATH} route ${route.skillPath} references unknown command intent "${intentName}"`);
|
|
@@ -843,6 +871,223 @@ function validateSkillIndexRoutes(projectRoot, commandsToml, skillFiles, issues)
|
|
|
843
871
|
validateSkillRouteMetadataAlignment(routeMetadata, routedSkillNames, expectedSkillNames, issues);
|
|
844
872
|
}
|
|
845
873
|
}
|
|
874
|
+
function templateFileContent(file) {
|
|
875
|
+
return file.content ?? readFileSync(file.sourcePath, 'utf8');
|
|
876
|
+
}
|
|
877
|
+
function skillRouteCategoryByLabel(label) {
|
|
878
|
+
for (const [category, categoryLabel] of Object.entries(SKILL_ROUTE_CATEGORY_LABELS)) {
|
|
879
|
+
if (categoryLabel === label) {
|
|
880
|
+
return category;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return undefined;
|
|
884
|
+
}
|
|
885
|
+
function splitSkillIndexTableRow(line) {
|
|
886
|
+
const trimmed = line.trim();
|
|
887
|
+
if (!trimmed.startsWith('|') || !trimmed.endsWith('|')) {
|
|
888
|
+
return undefined;
|
|
889
|
+
}
|
|
890
|
+
return trimmed.slice(1, -1).split('|').map((cell) => cell.trim());
|
|
891
|
+
}
|
|
892
|
+
function collectSkillIndexCategoryHeadings(content) {
|
|
893
|
+
const categories = new Set();
|
|
894
|
+
let currentSection;
|
|
895
|
+
for (const line of content.split(/\r?\n/u)) {
|
|
896
|
+
const heading = /^(#{2,3})\s+(.+?)\s*$/u.exec(line.trim());
|
|
897
|
+
if (!heading) {
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
const [, level, title] = heading;
|
|
901
|
+
if (level === '##') {
|
|
902
|
+
currentSection = title;
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
if (currentSection === 'Specific Routes' && level === '###') {
|
|
906
|
+
const category = skillRouteCategoryByLabel(title);
|
|
907
|
+
if (category) {
|
|
908
|
+
categories.add(category);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return categories;
|
|
913
|
+
}
|
|
914
|
+
function collectSkillIndexCategoryGateRows(content) {
|
|
915
|
+
const categories = new Set();
|
|
916
|
+
let currentSection;
|
|
917
|
+
for (const line of content.split(/\r?\n/u)) {
|
|
918
|
+
const heading = /^(#{2,3})\s+(.+?)\s*$/u.exec(line.trim());
|
|
919
|
+
if (heading) {
|
|
920
|
+
const [, level, title] = heading;
|
|
921
|
+
if (level === '##') {
|
|
922
|
+
currentSection = title;
|
|
923
|
+
}
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
if (currentSection !== 'Route Category Gate') {
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
const cells = splitSkillIndexTableRow(line);
|
|
930
|
+
if (!cells || cells.length === 0 || /^:?-{3,}:?$/u.test(cells[0] ?? '') || cells[0] === 'Category') {
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
const category = skillRouteCategoryByLabel(cells[0] ?? '');
|
|
934
|
+
if (category) {
|
|
935
|
+
categories.add(category);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return categories;
|
|
939
|
+
}
|
|
940
|
+
function readSkillRouteMetadataFromTomlContent(content, sourceLabel, issues) {
|
|
941
|
+
let parsed;
|
|
942
|
+
try {
|
|
943
|
+
parsed = parseTomlText(content);
|
|
944
|
+
}
|
|
945
|
+
catch (error) {
|
|
946
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
947
|
+
pushStrictIssue(issues, `Invalid TOML in ${sourceLabel}: ${message}`);
|
|
948
|
+
return new Map();
|
|
949
|
+
}
|
|
950
|
+
if (!isRecord(parsed)) {
|
|
951
|
+
pushStrictIssue(issues, `${sourceLabel} must contain a TOML table`);
|
|
952
|
+
return new Map();
|
|
953
|
+
}
|
|
954
|
+
if (parsed.schema_version !== '1') {
|
|
955
|
+
pushStrictIssue(issues, `${sourceLabel} schema_version must be "1"`);
|
|
956
|
+
}
|
|
957
|
+
if (!isRecord(parsed.routes)) {
|
|
958
|
+
pushStrictIssue(issues, `${sourceLabel} must define a [routes] table`);
|
|
959
|
+
return new Map();
|
|
960
|
+
}
|
|
961
|
+
const metadata = new Map();
|
|
962
|
+
for (const [skillName, route] of Object.entries(parsed.routes)) {
|
|
963
|
+
if (!/^[a-z][a-z0-9-]*$/u.test(skillName)) {
|
|
964
|
+
pushStrictIssue(issues, `${sourceLabel} route key "${skillName}" must be a skill folder name`);
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
if (!isRecord(route)) {
|
|
968
|
+
pushStrictIssue(issues, `${sourceLabel} routes.${skillName} must be a TOML table`);
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
metadata.set(skillName, validateSkillRouteMetadataTable(skillName, route, issues));
|
|
972
|
+
}
|
|
973
|
+
return metadata;
|
|
974
|
+
}
|
|
975
|
+
function validateTemplateProfileSkillRoutes(profile, files, issues) {
|
|
976
|
+
const contentByPath = new Map(files.map((file) => [file.relativePath, templateFileContent(file)]));
|
|
977
|
+
const indexContent = contentByPath.get(SKILL_INDEX_PATH);
|
|
978
|
+
const routesContent = contentByPath.get(SKILL_ROUTES_METADATA_PATH);
|
|
979
|
+
const commandsContent = contentByPath.get('.mustflow/config/commands.toml');
|
|
980
|
+
if (!indexContent) {
|
|
981
|
+
pushStrictIssue(issues, `template profile "${profile}" is missing generated ${SKILL_INDEX_PATH}`);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
if (!routesContent) {
|
|
985
|
+
pushStrictIssue(issues, `template profile "${profile}" is missing generated ${SKILL_ROUTES_METADATA_PATH}`);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
let commandsToml;
|
|
989
|
+
if (commandsContent) {
|
|
990
|
+
try {
|
|
991
|
+
const parsedCommands = parseTomlText(commandsContent);
|
|
992
|
+
if (isRecord(parsedCommands)) {
|
|
993
|
+
commandsToml = parsedCommands;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
catch {
|
|
997
|
+
commandsToml = undefined;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
const routeMetadata = readSkillRouteMetadataFromTomlContent(routesContent, `template profile "${profile}" ${SKILL_ROUTES_METADATA_PATH}`, issues);
|
|
1001
|
+
const skillRoutes = parseSkillIndexRoutes(indexContent);
|
|
1002
|
+
const categoryHeadings = collectSkillIndexCategoryHeadings(indexContent);
|
|
1003
|
+
const categoryGateRows = collectSkillIndexCategoryGateRows(indexContent);
|
|
1004
|
+
const routesByCategory = new Map();
|
|
1005
|
+
const selectedSkillContents = new Map();
|
|
1006
|
+
const routedSkillNames = new Set();
|
|
1007
|
+
for (const [relativePath, content] of contentByPath.entries()) {
|
|
1008
|
+
const skillName = skillRouteName(relativePath);
|
|
1009
|
+
if (skillName) {
|
|
1010
|
+
selectedSkillContents.set(skillName, content);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
for (const route of skillRoutes) {
|
|
1014
|
+
const routeSkillName = skillRouteName(route.skillPath);
|
|
1015
|
+
if (!routeSkillName) {
|
|
1016
|
+
pushStrictIssue(issues, `template profile "${profile}" ${SKILL_INDEX_PATH} route "${route.skillPath}" must point to .mustflow/skills/<name>/SKILL.md`);
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
routedSkillNames.add(routeSkillName);
|
|
1020
|
+
const metadata = routeMetadata.get(routeSkillName);
|
|
1021
|
+
if (!selectedSkillContents.has(routeSkillName)) {
|
|
1022
|
+
pushStrictIssue(issues, `template profile "${profile}" ${SKILL_INDEX_PATH} route "${routeSkillName}" points to a skill not installed by that profile`);
|
|
1023
|
+
}
|
|
1024
|
+
if (!metadata) {
|
|
1025
|
+
pushStrictIssue(issues, `template profile "${profile}" ${SKILL_ROUTES_METADATA_PATH} is missing metadata for route "${routeSkillName}"`);
|
|
1026
|
+
}
|
|
1027
|
+
if (metadata?.category && route.category !== metadata.category) {
|
|
1028
|
+
pushStrictIssue(issues, `template profile "${profile}" ${SKILL_INDEX_PATH} route "${routeSkillName}" must appear under the ${SKILL_ROUTE_CATEGORY_LABELS[metadata.category]} category section from ${SKILL_ROUTES_METADATA_PATH}`);
|
|
1029
|
+
}
|
|
1030
|
+
if (metadata?.category) {
|
|
1031
|
+
routesByCategory.set(metadata.category, [...(routesByCategory.get(metadata.category) ?? []), routeSkillName]);
|
|
1032
|
+
}
|
|
1033
|
+
const skillContent = selectedSkillContents.get(routeSkillName);
|
|
1034
|
+
const skillCommandIntents = new Set(skillContent ? readFrontmatterList(skillContent, 'command_intents') : []);
|
|
1035
|
+
for (const intentName of route.commandIntents) {
|
|
1036
|
+
if (commandsToml && !isDeclaredCommandIntent(commandsToml, intentName)) {
|
|
1037
|
+
pushStrictIssue(issues, `template profile "${profile}" ${SKILL_INDEX_PATH} route "${routeSkillName}" references unknown command intent "${intentName}"`);
|
|
1038
|
+
}
|
|
1039
|
+
if (!skillCommandIntents.has(intentName)) {
|
|
1040
|
+
pushStrictIssue(issues, `template profile "${profile}" ${SKILL_INDEX_PATH} route "${routeSkillName}" references command intent "${intentName}" not declared by the skill frontmatter`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
for (const [skillName, metadata] of routeMetadata.entries()) {
|
|
1045
|
+
if (!routedSkillNames.has(skillName)) {
|
|
1046
|
+
pushStrictIssue(issues, `template profile "${profile}" ${SKILL_ROUTES_METADATA_PATH} route "${skillName}" is not listed in ${SKILL_INDEX_PATH}`);
|
|
1047
|
+
}
|
|
1048
|
+
if (!selectedSkillContents.has(skillName)) {
|
|
1049
|
+
pushStrictIssue(issues, `template profile "${profile}" ${SKILL_ROUTES_METADATA_PATH} route "${skillName}" points to a skill not installed by that profile`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
for (const skillName of selectedSkillContents.keys()) {
|
|
1053
|
+
if (!routedSkillNames.has(skillName)) {
|
|
1054
|
+
pushStrictIssue(issues, `template profile "${profile}" skill "${skillName}" is installed but not listed in ${SKILL_INDEX_PATH}`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
for (const category of categoryHeadings) {
|
|
1058
|
+
if (!routesByCategory.has(category)) {
|
|
1059
|
+
pushStrictIssue(issues, `template profile "${profile}" skill index category "${SKILL_ROUTE_CATEGORY_LABELS[category]}" has no route rows`);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
for (const category of categoryGateRows) {
|
|
1063
|
+
if (!routesByCategory.has(category)) {
|
|
1064
|
+
pushStrictIssue(issues, `template profile "${profile}" route category gate references "${SKILL_ROUTE_CATEGORY_LABELS[category]}" without route rows`);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
for (const [category, skillNames] of routesByCategory.entries()) {
|
|
1068
|
+
const hasSelectableMainRoute = skillNames.some((skillName) => {
|
|
1069
|
+
const routeType = routeMetadata.get(skillName)?.routeType;
|
|
1070
|
+
return routeType === 'primary' || routeType === 'authoring';
|
|
1071
|
+
});
|
|
1072
|
+
if (!hasSelectableMainRoute) {
|
|
1073
|
+
pushStrictIssue(issues, `template profile "${profile}" skill category "${SKILL_ROUTE_CATEGORY_LABELS[category]}" must include at least one primary or authoring route`);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
function validateStrictTemplateSkillProfiles(issues) {
|
|
1078
|
+
let template;
|
|
1079
|
+
try {
|
|
1080
|
+
template = getDefaultTemplate();
|
|
1081
|
+
}
|
|
1082
|
+
catch (error) {
|
|
1083
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1084
|
+
pushStrictIssue(issues, `default template skill profiles could not be loaded: ${message}`);
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
for (const profile of template.manifest.profiles) {
|
|
1088
|
+
validateTemplateProfileSkillRoutes(profile, getTemplateFiles(template, template.manifest.defaultLocale, profile), issues);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
846
1091
|
function listManagedMarkdownDocuments(projectRoot) {
|
|
847
1092
|
const documents = [];
|
|
848
1093
|
for (const relativePath of ['AGENTS.md', '.mustflow/skills/INDEX.md']) {
|
|
@@ -872,7 +1117,10 @@ function validateStrictManagedMarkdownIdentities(projectRoot, issues) {
|
|
|
872
1117
|
if (!expectation) {
|
|
873
1118
|
continue;
|
|
874
1119
|
}
|
|
875
|
-
const content =
|
|
1120
|
+
const content = readStrictMustflowText(projectRoot, relativePath, issues);
|
|
1121
|
+
if (content === undefined) {
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
876
1124
|
const frontmatter = parseSimpleFrontmatter(content);
|
|
877
1125
|
const documentLabel = formatManagedMarkdownLabel(relativePath, expectation);
|
|
878
1126
|
if (frontmatter.mustflow_doc !== expectation.docId) {
|
|
@@ -964,7 +1212,7 @@ function validateStrictCandidateContractModelConfigs(projectRoot, issues) {
|
|
|
964
1212
|
}
|
|
965
1213
|
let parsed;
|
|
966
1214
|
try {
|
|
967
|
-
parsed =
|
|
1215
|
+
parsed = readMustflowTomlFile(projectRoot, model.filePath);
|
|
968
1216
|
}
|
|
969
1217
|
catch (error) {
|
|
970
1218
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1015,9 +1263,9 @@ function listSkillDirectories(skillsRoot) {
|
|
|
1015
1263
|
}
|
|
1016
1264
|
return [...skillNames].sort();
|
|
1017
1265
|
}
|
|
1018
|
-
function readSkillResourceManifest(
|
|
1266
|
+
function readSkillResourceManifest(projectRoot, manifestLabel, issues) {
|
|
1019
1267
|
try {
|
|
1020
|
-
const parsed =
|
|
1268
|
+
const parsed = readMustflowTomlFile(projectRoot, manifestLabel);
|
|
1021
1269
|
if (!isRecord(parsed)) {
|
|
1022
1270
|
pushStrictIssue(issues, `${manifestLabel} must contain a TOML table`);
|
|
1023
1271
|
return undefined;
|
|
@@ -1099,13 +1347,13 @@ function validateSkillResourceTable(skillDir, manifestLabel, resourcePath, resou
|
|
|
1099
1347
|
}
|
|
1100
1348
|
return normalizedPath;
|
|
1101
1349
|
}
|
|
1102
|
-
function validateSkillResourceManifest(skillDir, manifestLabel, commandsToml, issues) {
|
|
1350
|
+
function validateSkillResourceManifest(projectRoot, skillDir, manifestLabel, commandsToml, issues) {
|
|
1103
1351
|
const declaredResources = new Set();
|
|
1104
1352
|
const manifestPath = path.join(skillDir, SKILL_RESOURCE_MANIFEST);
|
|
1105
1353
|
if (!existsSync(manifestPath)) {
|
|
1106
1354
|
return declaredResources;
|
|
1107
1355
|
}
|
|
1108
|
-
const manifest = readSkillResourceManifest(
|
|
1356
|
+
const manifest = readSkillResourceManifest(projectRoot, manifestLabel, issues);
|
|
1109
1357
|
if (!manifest) {
|
|
1110
1358
|
return declaredResources;
|
|
1111
1359
|
}
|
|
@@ -1173,13 +1421,15 @@ function validateStrictSkills(projectRoot, commandsToml, issues) {
|
|
|
1173
1421
|
pushStrictIssue(issues, `.mustflow/skills/${skillName} is a skill folder without SKILL.md`);
|
|
1174
1422
|
}
|
|
1175
1423
|
const manifestLabel = `.mustflow/skills/${skillName}/${SKILL_RESOURCE_MANIFEST}`;
|
|
1176
|
-
const declaredResources = validateSkillResourceManifest(skillDir, manifestLabel, commandsToml, issues);
|
|
1424
|
+
const declaredResources = validateSkillResourceManifest(projectRoot, skillDir, manifestLabel, commandsToml, issues);
|
|
1177
1425
|
validateDeclaredSkillScripts(skillDir, skillName, declaredResources, issues);
|
|
1178
1426
|
}
|
|
1179
1427
|
for (const relativePath of skillFiles) {
|
|
1180
|
-
const absolutePath = path.join(skillsRoot, relativePath);
|
|
1181
|
-
const content = readFileSync(absolutePath, 'utf8');
|
|
1182
1428
|
const normalizedRelativePath = toPosixPath(relativePath);
|
|
1429
|
+
const content = readStrictMustflowText(projectRoot, `.mustflow/skills/${normalizedRelativePath}`, issues);
|
|
1430
|
+
if (content === undefined) {
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1183
1433
|
const skillName = normalizedRelativePath.split('/')[0] ?? '';
|
|
1184
1434
|
const skillLabel = `.mustflow/skills/${normalizedRelativePath}`;
|
|
1185
1435
|
const frontmatter = parseSimpleFrontmatter(content);
|
|
@@ -1206,7 +1456,10 @@ function validateStrictRepoMap(projectRoot, issues) {
|
|
|
1206
1456
|
if (!existsSync(repoMapPath)) {
|
|
1207
1457
|
return;
|
|
1208
1458
|
}
|
|
1209
|
-
const content =
|
|
1459
|
+
const content = readStrictMustflowText(projectRoot, 'REPO_MAP.md', issues);
|
|
1460
|
+
if (content === undefined) {
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1210
1463
|
const frontmatter = parseSimpleFrontmatter(content);
|
|
1211
1464
|
if (frontmatter.mustflow_doc !== REPO_MAP_DOC_ID) {
|
|
1212
1465
|
pushStrictIssue(issues, `REPO_MAP.md frontmatter mustflow_doc must be "${REPO_MAP_DOC_ID}"`);
|
|
@@ -1229,6 +1482,12 @@ function validateStrictRepoMap(projectRoot, issues) {
|
|
|
1229
1482
|
if (!/^[1-9]\d*$/u.test(frontmatter.anchor_count ?? '')) {
|
|
1230
1483
|
pushStrictIssue(issues, 'REPO_MAP.md frontmatter anchor_count must be a positive integer');
|
|
1231
1484
|
}
|
|
1485
|
+
if (!ALLOWED_REPO_MAP_DEGRADED_VALUES.has(frontmatter.degraded ?? '')) {
|
|
1486
|
+
pushStrictIssue(issues, 'REPO_MAP.md frontmatter degraded must be true or false');
|
|
1487
|
+
}
|
|
1488
|
+
if (!ALLOWED_REPO_MAP_GIT_LS_FILES_STATUSES.has(frontmatter.git_ls_files_status ?? '')) {
|
|
1489
|
+
pushStrictIssue(issues, 'REPO_MAP.md frontmatter git_ls_files_status must be ok, timeout, max_buffer, or error');
|
|
1490
|
+
}
|
|
1232
1491
|
if (!REPO_MAP_SOURCE_FINGERPRINT_PATTERN.test(frontmatter.source_fingerprint ?? '')) {
|
|
1233
1492
|
pushStrictIssue(issues, 'REPO_MAP.md frontmatter source_fingerprint must be sha256:<64 lowercase hex characters>');
|
|
1234
1493
|
}
|
|
@@ -1253,7 +1512,10 @@ function validateStrictContextDocuments(projectRoot, limits, issues) {
|
|
|
1253
1512
|
for (const relativePath of contextFiles) {
|
|
1254
1513
|
const normalizedPath = `.mustflow/context/${toPosixPath(relativePath)}`;
|
|
1255
1514
|
const absolutePath = path.join(contextRoot, relativePath);
|
|
1256
|
-
const content =
|
|
1515
|
+
const content = readStrictMustflowText(projectRoot, normalizedPath, issues);
|
|
1516
|
+
if (content === undefined) {
|
|
1517
|
+
continue;
|
|
1518
|
+
}
|
|
1257
1519
|
if (exceedsKiBLimit(absolutePath, limits.contextMaxFileKb)) {
|
|
1258
1520
|
pushStrictIssue(issues, formatStorageLimitMessage(normalizedPath, '[retention.context].max_file_kb', absolutePath, limits.contextMaxFileKb));
|
|
1259
1521
|
}
|
|
@@ -1293,7 +1555,13 @@ function validateStrictRunReceipt(projectRoot, issues) {
|
|
|
1293
1555
|
return;
|
|
1294
1556
|
}
|
|
1295
1557
|
try {
|
|
1296
|
-
const
|
|
1558
|
+
const content = readStrictMustflowText(projectRoot, '.mustflow/state/runs/latest.json', issues, {
|
|
1559
|
+
maxBytes: MUSTFLOW_JSON_MAX_BYTES,
|
|
1560
|
+
});
|
|
1561
|
+
if (content === undefined) {
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
const parsed = JSON.parse(content);
|
|
1297
1565
|
if (!isRecord(parsed)) {
|
|
1298
1566
|
pushStrictIssue(issues, '.mustflow/state/runs/latest.json must contain a JSON object');
|
|
1299
1567
|
}
|
|
@@ -1348,6 +1616,7 @@ function validateStrict(projectRoot, parsed, issues) {
|
|
|
1348
1616
|
validateStrictManagedMarkdownIdentities(projectRoot, issues);
|
|
1349
1617
|
validateStrictRouterIndexes(projectRoot, issues);
|
|
1350
1618
|
validateStrictSkills(projectRoot, parsed.commandsToml, issues);
|
|
1619
|
+
validateStrictTemplateSkillProfiles(issues);
|
|
1351
1620
|
validateStrictRepoMap(projectRoot, issues);
|
|
1352
1621
|
validateStrictContextDocuments(projectRoot, retentionLimits, issues);
|
|
1353
1622
|
validateStrictSourceAnchors(projectRoot, issues);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isRecord } from '../command-contract.js';
|
|
4
|
-
import {
|
|
4
|
+
import { readMustflowTomlFile } from '../toml.js';
|
|
5
5
|
import { REQUIRED_FILES, } from './constants.js';
|
|
6
6
|
import { VERSIONING_CONFIG_PATH } from '../../../core/version-sources.js';
|
|
7
7
|
export function hasOwn(table, key) {
|
|
@@ -41,7 +41,7 @@ export function validateToml(projectRoot, issues) {
|
|
|
41
41
|
continue;
|
|
42
42
|
}
|
|
43
43
|
try {
|
|
44
|
-
const parsed =
|
|
44
|
+
const parsed = readMustflowTomlFile(projectRoot, relativePath);
|
|
45
45
|
if (!isRecord(parsed)) {
|
|
46
46
|
issues.push({ message: `${relativePath} must contain a TOML table` });
|
|
47
47
|
continue;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isRecord } from '../command-contract.js';
|
|
4
|
-
import {
|
|
4
|
+
import { readMustflowTomlFile } from '../toml.js';
|
|
5
5
|
import { ALLOWED_TEST_SELECTION_RISKS, FORBIDDEN_TEST_SELECTION_COMMAND_AUTHORITY_FIELDS, TEST_SELECTION_CONFIG_PATH, } from './constants.js';
|
|
6
6
|
import { isConfiguredCommandIntent, isDeclaredCommandIntent } from './command-intents.js';
|
|
7
7
|
import { hasOwn, pushStrictIssue, validateAllowedStringField, validateNestedTable, validatePathArrayField, validateRequiredStringField, validateStringArrayField, } from './primitives.js';
|
|
@@ -69,7 +69,7 @@ export function validateStrictTestSelectionConfig(projectRoot, commandsToml, iss
|
|
|
69
69
|
}
|
|
70
70
|
let parsed;
|
|
71
71
|
try {
|
|
72
|
-
parsed =
|
|
72
|
+
parsed = readMustflowTomlFile(projectRoot, TEST_SELECTION_CONFIG_PATH);
|
|
73
73
|
}
|
|
74
74
|
catch (error) {
|
|
75
75
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -22,6 +22,8 @@ export function decodeUtf8Tail(buffer, maxTailBytes) {
|
|
|
22
22
|
export class BoundedOutputBuffer {
|
|
23
23
|
#maxTailBytes;
|
|
24
24
|
#chunks = [];
|
|
25
|
+
#headIndex = 0;
|
|
26
|
+
#headOffset = 0;
|
|
25
27
|
#tailBytes = 0;
|
|
26
28
|
#bytes = 0;
|
|
27
29
|
constructor(maxTailBytes) {
|
|
@@ -35,26 +37,49 @@ export class BoundedOutputBuffer {
|
|
|
35
37
|
}
|
|
36
38
|
this.#chunks.push(buffer);
|
|
37
39
|
this.#tailBytes += buffer.byteLength;
|
|
38
|
-
while (this.#tailBytes > this.#maxTailBytes && this.#chunks.length
|
|
39
|
-
const first = this.#chunks[
|
|
40
|
+
while (this.#tailBytes > this.#maxTailBytes && this.#headIndex < this.#chunks.length) {
|
|
41
|
+
const first = this.#chunks[this.#headIndex];
|
|
40
42
|
const overflow = this.#tailBytes - this.#maxTailBytes;
|
|
41
43
|
if (!first) {
|
|
42
44
|
break;
|
|
43
45
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.#
|
|
46
|
+
const firstAvailableBytes = first.byteLength - this.#headOffset;
|
|
47
|
+
if (firstAvailableBytes <= overflow) {
|
|
48
|
+
this.#headIndex += 1;
|
|
49
|
+
this.#headOffset = 0;
|
|
50
|
+
this.#tailBytes -= firstAvailableBytes;
|
|
47
51
|
continue;
|
|
48
52
|
}
|
|
49
|
-
this.#
|
|
53
|
+
this.#headOffset += overflow;
|
|
50
54
|
this.#tailBytes -= overflow;
|
|
51
55
|
}
|
|
56
|
+
this.#compactRetainedChunks();
|
|
52
57
|
}
|
|
53
58
|
toSnapshot() {
|
|
54
|
-
const tail = decodeUtf8Tail(Buffer.concat(this.#
|
|
59
|
+
const tail = decodeUtf8Tail(Buffer.concat(this.#retainedChunks(), this.#tailBytes), this.#maxTailBytes);
|
|
55
60
|
return {
|
|
56
61
|
bytes: this.#bytes,
|
|
57
62
|
tail: tail.text,
|
|
58
63
|
};
|
|
59
64
|
}
|
|
65
|
+
#retainedChunks() {
|
|
66
|
+
if (this.#tailBytes === 0 || this.#headIndex >= this.#chunks.length) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const chunks = this.#chunks.slice(this.#headIndex);
|
|
70
|
+
if (this.#headOffset > 0 && chunks[0]) {
|
|
71
|
+
chunks[0] = chunks[0].subarray(this.#headOffset);
|
|
72
|
+
}
|
|
73
|
+
return chunks;
|
|
74
|
+
}
|
|
75
|
+
#compactRetainedChunks() {
|
|
76
|
+
if (this.#headIndex === 0) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (this.#headIndex < 64 && this.#headIndex * 2 < this.#chunks.length) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.#chunks = this.#chunks.slice(this.#headIndex);
|
|
83
|
+
this.#headIndex = 0;
|
|
84
|
+
}
|
|
60
85
|
}
|
|
@@ -7,6 +7,7 @@ const CHECK_ISSUE_ID_RULES = [
|
|
|
7
7
|
['mustflow.command_contract.max_output_bytes_exceeds_limit', /^\[commands\.(?:defaults|intents\.[^\]]+)\]\.max_output_bytes must be less than or equal to \d+ per output stream$/u],
|
|
8
8
|
['mustflow.command_contract.oneshot_stdin_not_closed', /^Oneshot intent [^\s]+ must set stdin = "closed"$/u],
|
|
9
9
|
['mustflow.command_contract.long_running_agent_allowed', /^Long-running intent [^\s]+ must not use run_policy = "agent_allowed"$/u],
|
|
10
|
+
['mustflow.command_contract.agent_shell_requires_allow', /^Agent-runnable shell intent [^\s]+ must set allow_shell = true$/u],
|
|
10
11
|
['mustflow.command_contract.executable_source_missing', /^Configured intent [^\s]+ must define argv or mode = "shell" with cmd$/u],
|
|
11
12
|
['mustflow.command_contract.shell_background_pattern', /^Shell intent [^\s]+ contains a blocked long-running or background pattern$/u],
|
|
12
13
|
['mustflow.command_contract.long_running_command_pattern', /^Intent [^\s]+ contains a blocked long-running or background command pattern$/u],
|
|
@@ -17,7 +18,7 @@ const CHECK_ISSUE_ID_RULES = [
|
|
|
17
18
|
['mustflow.command_contract.effects_invalid', /^(?:Strict: )?(?:\[commands\.(?:resources|intents\.[^\]]+\.effects)[^\]]*\]|Command effect for intent [^\s]+ must define path, paths, or lock)/u],
|
|
18
19
|
['mustflow.command_contract.effect_path_escape', /^Strict: Command effect path must stay inside the current root:/u],
|
|
19
20
|
['mustflow.command_contract.shared_writes_without_effects', /^Strict warning: configured agent-runnable intents .+ share path:.+ through writes without explicit effects or resource locks$/u],
|
|
20
|
-
['mustflow.command_contract.broad_env_inheritance', /^Strict warning
|
|
21
|
+
['mustflow.command_contract.broad_env_inheritance', /^Strict(?: warning)?: configured agent-runnable intent [^\s]+ (?:implicitly inherits the host environment|uses env_policy = "inherit")/u],
|
|
21
22
|
['mustflow.command_contract.project_local_bin_bare_executable', /^Strict warning: configured agent-runnable intent [^\s]+ uses bare executable "[^"]+" that matches project-local node_modules\/\.bin/u],
|
|
22
23
|
['mustflow.prompt_cache.required', /^Strict: \[prompt_cache\] table is required$/u],
|
|
23
24
|
['mustflow.prompt_cache.volatile_in_stable', /^Strict: \[prompt_cache\.layers\.stable\]\.read must not include volatile path /u],
|
|
@@ -48,6 +49,11 @@ const CHECK_ISSUE_ID_RULES = [
|
|
|
48
49
|
['mustflow.skill.route_metadata_category_mismatch', /^Strict: \.mustflow\/skills\/INDEX\.md route "[^"]+" must appear under the .+ category section from \.mustflow\/skills\/routes\.toml$/u],
|
|
49
50
|
['mustflow.skill.route_metadata_unknown_reference', /^Strict: \.mustflow\/skills\/routes\.toml route "[^"]+" references unknown mutually exclusive route "[^"]+"$/u],
|
|
50
51
|
['mustflow.skill.route_metadata_asymmetric_exclusion', /^Strict warning: \.mustflow\/skills\/routes\.toml route "[^"]+" lists "[^"]+" as mutually exclusive but the reverse route does not$/u],
|
|
52
|
+
['mustflow.skill.template_profile_empty_category', /^Strict: template profile "[^"]+" (?:skill index category ".+" has no route rows|route category gate references ".+" without route rows)$/u],
|
|
53
|
+
['mustflow.skill.template_profile_dead_route', /^Strict: template profile "[^"]+" (?:\.mustflow\/skills\/INDEX\.md route "[^"]+" points to a skill not installed by that profile|\.mustflow\/skills\/routes\.toml route "[^"]+" points to a skill not installed by that profile|skill "[^"]+" is installed but not listed in \.mustflow\/skills\/INDEX\.md)$/u],
|
|
54
|
+
['mustflow.skill.template_profile_missing_main_route', /^Strict: template profile "[^"]+" skill category ".+" must include at least one primary or authoring route$/u],
|
|
55
|
+
['mustflow.skill.template_profile_command_intent_drift', /^Strict: template profile "[^"]+" \.mustflow\/skills\/INDEX\.md route "[^"]+" references (?:unknown command intent "[^"]+"|command intent "[^"]+" not declared by the skill frontmatter)$/u],
|
|
56
|
+
['mustflow.skill.template_profile_metadata_mismatch', /^Strict: template profile "[^"]+" (?:\.mustflow\/skills\/routes\.toml is missing metadata for route "[^"]+"|\.mustflow\/skills\/routes\.toml route "[^"]+" is not listed in \.mustflow\/skills\/INDEX\.md|\.mustflow\/skills\/INDEX\.md route "[^"]+" must appear under the .+ category section from \.mustflow\/skills\/routes\.toml)$/u],
|
|
51
57
|
['mustflow.skill.resource_unknown_command_intent', /^Strict: \.mustflow\/skills\/[^/]+\/resources\.toml script [^\s]+ references unknown command intent "[^"]+"$/u],
|
|
52
58
|
['mustflow.source_anchor.invalid_format', /^Strict: source anchor .+ has invalid format:/u],
|
|
53
59
|
['mustflow.source_anchor.duplicate_id', /^Strict: source anchor id "[^"]+" is duplicated:/u],
|