mustflow 2.31.0 → 2.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/api/actions.js +55 -0
- package/dist/cli/commands/api/report-runner.js +62 -0
- package/dist/cli/commands/api/serve.js +149 -0
- package/dist/cli/commands/api/workspace-recommendations.js +13 -0
- package/dist/cli/commands/api.js +15 -275
- package/dist/cli/lib/local-index/search-read-model.js +44 -7
- package/dist/cli/lib/validation/frontmatter.js +75 -0
- package/dist/cli/lib/validation/index.js +4 -86
- package/dist/cli/lib/validation/safe-read.js +13 -0
- package/dist/core/active-run-locks.js +110 -10
- package/dist/core/run-performance-history.js +14 -1
- package/dist/core/validation-ratchet.js +1 -1
- package/package.json +1 -1
- package/templates/default/i18n.toml +63 -21
- package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +15 -7
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +21 -8
- package/templates/default/locales/en/.mustflow/skills/adapter-boundary/SKILL.md +9 -2
- package/templates/default/locales/en/.mustflow/skills/ai-generated-code-hardening/SKILL.md +249 -0
- package/templates/default/locales/en/.mustflow/skills/api-contract-change/SKILL.md +16 -11
- package/templates/default/locales/en/.mustflow/skills/auth-permission-change/SKILL.md +11 -4
- package/templates/default/locales/en/.mustflow/skills/backend-reliability-change/SKILL.md +289 -0
- package/templates/default/locales/en/.mustflow/skills/css-code-change/SKILL.md +24 -14
- package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +18 -7
- package/templates/default/locales/en/.mustflow/skills/frontend-render-stability/SKILL.md +144 -0
- package/templates/default/locales/en/.mustflow/skills/go-code-change/SKILL.md +70 -18
- package/templates/default/locales/en/.mustflow/skills/heuristic-candidate-selection/SKILL.md +165 -0
- package/templates/default/locales/en/.mustflow/skills/html-code-change/SKILL.md +20 -13
- package/templates/default/locales/en/.mustflow/skills/http-delivery-streaming/SKILL.md +205 -0
- package/templates/default/locales/en/.mustflow/skills/performance-budget-check/SKILL.md +9 -7
- package/templates/default/locales/en/.mustflow/skills/proactive-risk-surfacing/SKILL.md +198 -0
- package/templates/default/locales/en/.mustflow/skills/python-code-change/SKILL.md +27 -11
- package/templates/default/locales/en/.mustflow/skills/routes.toml +43 -1
- package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +41 -17
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +4 -1
- package/templates/default/locales/en/.mustflow/skills/security-regression-tests/SKILL.md +5 -2
- package/templates/default/locales/en/.mustflow/skills/service-boundary-architecture/SKILL.md +167 -0
- package/templates/default/locales/en/.mustflow/skills/tailwind-code-change/SKILL.md +37 -23
- package/templates/default/locales/en/.mustflow/skills/tauri-code-change/SKILL.md +27 -10
- package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +22 -4
- package/templates/default/locales/en/.mustflow/skills/unocss-code-change/SKILL.md +34 -15
- package/templates/default/locales/en/.mustflow/skills/version-freshness-check/SKILL.md +29 -5
- package/templates/default/locales/en/AGENTS.md +3 -2
- package/templates/default/locales/ko/.mustflow/docs/agent-workflow.md +13 -8
- package/templates/default/locales/ko/AGENTS.md +2 -2
- package/templates/default/manifest.toml +44 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { SKILL_SECTION_MARKER_PATTERN } from './constants.js';
|
|
2
|
+
export function readSkillSectionIds(content) {
|
|
3
|
+
return new Set([...content.matchAll(SKILL_SECTION_MARKER_PATTERN)].map((match) => match[1]));
|
|
4
|
+
}
|
|
5
|
+
function findFrontmatterEnd(content) {
|
|
6
|
+
const match = /\n---(?:\r?\n|$)/u.exec(content.slice(3));
|
|
7
|
+
return match ? 3 + match.index : -1;
|
|
8
|
+
}
|
|
9
|
+
export function parseSimpleFrontmatter(content) {
|
|
10
|
+
if (!content.startsWith('---')) {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
const end = findFrontmatterEnd(content);
|
|
14
|
+
if (end === -1) {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
const frontmatter = {};
|
|
18
|
+
for (const line of content.slice(3, end).split(/\r?\n/)) {
|
|
19
|
+
const separatorIndex = line.indexOf(':');
|
|
20
|
+
if (separatorIndex === -1) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
24
|
+
const value = line.slice(separatorIndex + 1).trim().replace(/^["']|["']$/g, '');
|
|
25
|
+
if (key.length > 0 && value.length > 0) {
|
|
26
|
+
frontmatter[key] = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return frontmatter;
|
|
30
|
+
}
|
|
31
|
+
function readFrontmatterLines(content) {
|
|
32
|
+
if (!content.startsWith('---')) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const end = findFrontmatterEnd(content);
|
|
36
|
+
if (end === -1) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
return content
|
|
40
|
+
.slice(3, end)
|
|
41
|
+
.split(/\n/u)
|
|
42
|
+
.map((line) => line.replace(/\r$/u, ''));
|
|
43
|
+
}
|
|
44
|
+
function stripScalarMarkers(value) {
|
|
45
|
+
return value.trim().replace(/^["'`]|["'`]$/g, '').trim();
|
|
46
|
+
}
|
|
47
|
+
export function readFrontmatterList(content, key) {
|
|
48
|
+
const lines = readFrontmatterLines(content);
|
|
49
|
+
const values = [];
|
|
50
|
+
let keyIndent;
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
const keyMatch = line.match(new RegExp(`^(\\s*)${key}:\\s*$`, 'u'));
|
|
53
|
+
if (keyIndent === undefined) {
|
|
54
|
+
if (keyMatch) {
|
|
55
|
+
keyIndent = keyMatch[1].length;
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (line.trim().length === 0) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const lineIndent = line.match(/^\s*/u)?.[0].length ?? 0;
|
|
63
|
+
const itemMatch = line.match(/^\s*-\s+(.+)$/u);
|
|
64
|
+
if (lineIndent <= keyIndent && !itemMatch) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (itemMatch) {
|
|
68
|
+
const value = stripScalarMarkers(itemMatch[1]);
|
|
69
|
+
if (value.length > 0) {
|
|
70
|
+
values.push(value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return values;
|
|
75
|
+
}
|
|
@@ -13,13 +13,15 @@ import { readGitChangedFiles } from '../git-changes.js';
|
|
|
13
13
|
import { inspectManifestLock } from '../manifest-lock.js';
|
|
14
14
|
import { generateRepoMap } from '../repo-map.js';
|
|
15
15
|
import { parseTomlText, readMustflowTomlFile } from '../toml.js';
|
|
16
|
-
import { MUSTFLOW_JSON_MAX_BYTES
|
|
16
|
+
import { MUSTFLOW_JSON_MAX_BYTES } from '../mustflow-read.js';
|
|
17
17
|
import { getContractModelDefinitions, validateCandidateContractModelConfig, } from '../../../core/contract-models.js';
|
|
18
18
|
import { VERSIONING_CONFIG_PATH, detectVersionSourcePaths, readDeclaredVersionSources, releaseVersioningIsEnabled, } from '../../../core/version-sources.js';
|
|
19
19
|
import { normalizeTechnologyPreferencesTable, TECHNOLOGY_CONFIG_RELATIVE_PATH, } from '../../../core/technology-preferences.js';
|
|
20
|
-
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,
|
|
20
|
+
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, SUPPORTED_SKILL_SCHEMA_VERSION, TEST_AUTHORING_BOOLEAN_FIELDS, VERIFICATION_SELECTION_BOOLEAN_FIELDS, VOLATILE_REPO_MAP_PATTERNS } from './constants.js';
|
|
21
21
|
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';
|
|
22
22
|
import { isConfiguredCommandIntent, isDeclaredCommandIntent } from './command-intents.js';
|
|
23
|
+
import { parseSimpleFrontmatter, readFrontmatterList, readSkillSectionIds } from './frontmatter.js';
|
|
24
|
+
import { readStrictMustflowText } from './safe-read.js';
|
|
23
25
|
import { validateStrictTestSelectionConfig } from './test-selection.js';
|
|
24
26
|
import { getDefaultTemplate, getTemplateFiles } from '../templates.js';
|
|
25
27
|
export { describeCheckIssues, getCheckIssueId, } from '../../../core/check-issues.js';
|
|
@@ -481,90 +483,6 @@ function validateSkills(projectRoot, issues) {
|
|
|
481
483
|
}
|
|
482
484
|
}
|
|
483
485
|
}
|
|
484
|
-
function readSkillSectionIds(content) {
|
|
485
|
-
return new Set([...content.matchAll(SKILL_SECTION_MARKER_PATTERN)].map((match) => match[1]));
|
|
486
|
-
}
|
|
487
|
-
function findFrontmatterEnd(content) {
|
|
488
|
-
const match = /\n---(?:\r?\n|$)/u.exec(content.slice(3));
|
|
489
|
-
return match ? 3 + match.index : -1;
|
|
490
|
-
}
|
|
491
|
-
function parseSimpleFrontmatter(content) {
|
|
492
|
-
if (!content.startsWith('---')) {
|
|
493
|
-
return {};
|
|
494
|
-
}
|
|
495
|
-
const end = findFrontmatterEnd(content);
|
|
496
|
-
if (end === -1) {
|
|
497
|
-
return {};
|
|
498
|
-
}
|
|
499
|
-
const frontmatter = {};
|
|
500
|
-
for (const line of content.slice(3, end).split(/\r?\n/)) {
|
|
501
|
-
const separatorIndex = line.indexOf(':');
|
|
502
|
-
if (separatorIndex === -1) {
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
const key = line.slice(0, separatorIndex).trim();
|
|
506
|
-
const value = line.slice(separatorIndex + 1).trim().replace(/^["']|["']$/g, '');
|
|
507
|
-
if (key.length > 0 && value.length > 0) {
|
|
508
|
-
frontmatter[key] = value;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
return frontmatter;
|
|
512
|
-
}
|
|
513
|
-
function readStrictMustflowText(projectRoot, relativePath, issues, options = {}) {
|
|
514
|
-
const result = readMustflowTextFileResult(projectRoot, relativePath, options);
|
|
515
|
-
if (result.ok) {
|
|
516
|
-
return result.content;
|
|
517
|
-
}
|
|
518
|
-
if (result.exists && result.error) {
|
|
519
|
-
pushStrictIssue(issues, `${toPosixPath(relativePath)} could not be read safely: ${result.error}`);
|
|
520
|
-
}
|
|
521
|
-
return undefined;
|
|
522
|
-
}
|
|
523
|
-
function readFrontmatterLines(content) {
|
|
524
|
-
if (!content.startsWith('---')) {
|
|
525
|
-
return [];
|
|
526
|
-
}
|
|
527
|
-
const end = findFrontmatterEnd(content);
|
|
528
|
-
if (end === -1) {
|
|
529
|
-
return [];
|
|
530
|
-
}
|
|
531
|
-
return content
|
|
532
|
-
.slice(3, end)
|
|
533
|
-
.split(/\n/u)
|
|
534
|
-
.map((line) => line.replace(/\r$/u, ''));
|
|
535
|
-
}
|
|
536
|
-
function stripScalarMarkers(value) {
|
|
537
|
-
return value.trim().replace(/^["'`]|["'`]$/g, '').trim();
|
|
538
|
-
}
|
|
539
|
-
function readFrontmatterList(content, key) {
|
|
540
|
-
const lines = readFrontmatterLines(content);
|
|
541
|
-
const values = [];
|
|
542
|
-
let keyIndent;
|
|
543
|
-
for (const line of lines) {
|
|
544
|
-
const keyMatch = line.match(new RegExp(`^(\\s*)${key}:\\s*$`, 'u'));
|
|
545
|
-
if (keyIndent === undefined) {
|
|
546
|
-
if (keyMatch) {
|
|
547
|
-
keyIndent = keyMatch[1].length;
|
|
548
|
-
}
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
if (line.trim().length === 0) {
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
const lineIndent = line.match(/^\s*/u)?.[0].length ?? 0;
|
|
555
|
-
const itemMatch = line.match(/^\s*-\s+(.+)$/u);
|
|
556
|
-
if (lineIndent <= keyIndent && !itemMatch) {
|
|
557
|
-
break;
|
|
558
|
-
}
|
|
559
|
-
if (itemMatch) {
|
|
560
|
-
const value = stripScalarMarkers(itemMatch[1]);
|
|
561
|
-
if (value.length > 0) {
|
|
562
|
-
values.push(value);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
return values;
|
|
567
|
-
}
|
|
568
486
|
function validateContextDocuments(projectRoot, issues) {
|
|
569
487
|
const contextRoot = path.join(projectRoot, '.mustflow', 'context');
|
|
570
488
|
const contextFiles = listFilesRecursive(contextRoot).filter((relativePath) => relativePath.endsWith('.md'));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { toPosixPath } from '../filesystem.js';
|
|
2
|
+
import { readMustflowTextFileResult } from '../mustflow-read.js';
|
|
3
|
+
import { pushStrictIssue } from './primitives.js';
|
|
4
|
+
export function readStrictMustflowText(projectRoot, relativePath, issues, options = {}) {
|
|
5
|
+
const result = readMustflowTextFileResult(projectRoot, relativePath, options);
|
|
6
|
+
if (result.ok) {
|
|
7
|
+
return result.content;
|
|
8
|
+
}
|
|
9
|
+
if (result.exists && result.error) {
|
|
10
|
+
pushStrictIssue(issues, `${toPosixPath(relativePath)} could not be read safely: ${result.error}`);
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
@@ -9,6 +9,7 @@ const LOCK_MUTEX_STALE_MS = 30_000;
|
|
|
9
9
|
const LOCK_MUTEX_WAIT_MS = 1_000;
|
|
10
10
|
const LOCK_MUTEX_SLEEP_MS = 25;
|
|
11
11
|
const LOCK_MUTEX_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
12
|
+
const LOCK_MUTEX_RECOVERY_DIRECTORY = 'recovery';
|
|
12
13
|
function sleep(milliseconds) {
|
|
13
14
|
Atomics.wait(LOCK_MUTEX_SLEEP_BUFFER, 0, 0, milliseconds);
|
|
14
15
|
}
|
|
@@ -24,6 +25,9 @@ function activeLockDirectory(projectRoot) {
|
|
|
24
25
|
function activeLockMutexDirectory(projectRoot) {
|
|
25
26
|
return path.join(activeLockRoot(projectRoot), 'mutex');
|
|
26
27
|
}
|
|
28
|
+
function activeLockMutexRecoveryDirectory(mutex) {
|
|
29
|
+
return path.join(mutex, LOCK_MUTEX_RECOVERY_DIRECTORY);
|
|
30
|
+
}
|
|
27
31
|
function normalizeEffect(effect) {
|
|
28
32
|
return {
|
|
29
33
|
source: effect.source,
|
|
@@ -192,6 +196,95 @@ function createRecord(projectRoot, intentName, effects, commandHash) {
|
|
|
192
196
|
writes: [...new Set(writes)],
|
|
193
197
|
};
|
|
194
198
|
}
|
|
199
|
+
function readMutexOwner(ownerPath) {
|
|
200
|
+
try {
|
|
201
|
+
const owner = JSON.parse(readFileSync(ownerPath, 'utf8'));
|
|
202
|
+
if (typeof owner.started_at !== 'string' || typeof owner.token !== 'string') {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
pid: Number(owner.pid),
|
|
207
|
+
startedAt: owner.started_at,
|
|
208
|
+
token: owner.token,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function sameMutexOwner(left, right) {
|
|
216
|
+
return right !== null && left.pid === right.pid && left.startedAt === right.startedAt && left.token === right.token;
|
|
217
|
+
}
|
|
218
|
+
function mutexOwnerIsStale(owner) {
|
|
219
|
+
const ownerStartedAt = Date.parse(owner.startedAt);
|
|
220
|
+
const staleByAge = Number.isFinite(ownerStartedAt) && Date.now() - ownerStartedAt > LOCK_MUTEX_STALE_MS;
|
|
221
|
+
return !isProcessLive(owner.pid) || staleByAge;
|
|
222
|
+
}
|
|
223
|
+
function beginMutexRecovery(mutex) {
|
|
224
|
+
const recoveryPath = activeLockMutexRecoveryDirectory(mutex);
|
|
225
|
+
try {
|
|
226
|
+
mkdirSync(recoveryPath);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'EEXIST') {
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const recoveryStat = statSync(recoveryPath);
|
|
234
|
+
if (Date.now() - recoveryStat.mtimeMs > LOCK_MUTEX_STALE_MS) {
|
|
235
|
+
rmSync(recoveryPath, { recursive: true, force: true });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// A concurrent recovery may have already finished.
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
let active = true;
|
|
244
|
+
return () => {
|
|
245
|
+
if (!active) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
active = false;
|
|
249
|
+
rmSync(recoveryPath, { recursive: true, force: true });
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function recoverStaleMutexWithOwner(mutex, ownerPath, staleOwner) {
|
|
253
|
+
const releaseRecovery = beginMutexRecovery(mutex);
|
|
254
|
+
if (!releaseRecovery) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
if (!sameMutexOwner(staleOwner, readMutexOwner(ownerPath)) || !mutexOwnerIsStale(staleOwner)) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
rmSync(mutex, { recursive: true, force: true });
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
releaseRecovery();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function recoverStaleMutexWithoutOwner(mutex) {
|
|
269
|
+
const releaseRecovery = beginMutexRecovery(mutex);
|
|
270
|
+
if (!releaseRecovery) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
const ownerPath = path.join(mutex, 'owner.json');
|
|
275
|
+
if (readMutexOwner(ownerPath) !== null) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
rmSync(mutex, { recursive: true, force: true });
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
releaseRecovery();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
195
288
|
function acquireMutex(projectRoot) {
|
|
196
289
|
const root = activeLockRoot(projectRoot);
|
|
197
290
|
const mutex = activeLockMutexDirectory(projectRoot);
|
|
@@ -227,22 +320,29 @@ function acquireMutex(projectRoot) {
|
|
|
227
320
|
throw error;
|
|
228
321
|
}
|
|
229
322
|
if (Date.now() - startedAt > LOCK_MUTEX_WAIT_MS) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const ownerStartedAt = typeof owner.started_at === 'string' ? Date.parse(owner.started_at) : Number.NaN;
|
|
234
|
-
const staleByAge = Number.isFinite(ownerStartedAt) && Date.now() - ownerStartedAt > LOCK_MUTEX_STALE_MS;
|
|
235
|
-
if (!isProcessLive(ownerPid) || staleByAge) {
|
|
236
|
-
rmSync(mutex, { recursive: true, force: true });
|
|
323
|
+
const owner = readMutexOwner(ownerPath);
|
|
324
|
+
if (owner) {
|
|
325
|
+
if (mutexOwnerIsStale(owner) && recoverStaleMutexWithOwner(mutex, ownerPath, owner)) {
|
|
237
326
|
startedAt = Date.now();
|
|
238
327
|
continue;
|
|
239
328
|
}
|
|
240
329
|
}
|
|
241
|
-
|
|
330
|
+
else {
|
|
331
|
+
const recoveryPath = activeLockMutexRecoveryDirectory(mutex);
|
|
332
|
+
try {
|
|
333
|
+
const recoveryStat = statSync(recoveryPath);
|
|
334
|
+
if (Date.now() - recoveryStat.mtimeMs <= LOCK_MUTEX_STALE_MS) {
|
|
335
|
+
throw new Error('active_run_lock_mutex_busy');
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (recoveryError) {
|
|
339
|
+
if (recoveryError instanceof Error && recoveryError.message === 'active_run_lock_mutex_busy') {
|
|
340
|
+
throw recoveryError;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
242
343
|
try {
|
|
243
344
|
const mutexStat = statSync(mutex);
|
|
244
|
-
if (Date.now() - mutexStat.mtimeMs > LOCK_MUTEX_STALE_MS) {
|
|
245
|
-
rmSync(mutex, { recursive: true, force: true });
|
|
345
|
+
if (Date.now() - mutexStat.mtimeMs > LOCK_MUTEX_STALE_MS && recoverStaleMutexWithoutOwner(mutex)) {
|
|
246
346
|
startedAt = Date.now();
|
|
247
347
|
continue;
|
|
248
348
|
}
|
|
@@ -281,12 +281,25 @@ function serializedHistorySize(samples, today) {
|
|
|
281
281
|
Buffer.byteLength(serialize(createSummary(samples, today)), 'utf8'));
|
|
282
282
|
}
|
|
283
283
|
function enforceSizeLimit(samples, today) {
|
|
284
|
-
|
|
284
|
+
const currentSize = serializedHistorySize(samples, today);
|
|
285
|
+
if (currentSize <= MAX_TOTAL_BYTES) {
|
|
285
286
|
return samples;
|
|
286
287
|
}
|
|
288
|
+
const averageBytesPerSample = Math.max(1, currentSize / Math.max(1, samples.length));
|
|
289
|
+
const estimatedDropCount = Math.floor((currentSize - MAX_TOTAL_BYTES) / averageBytesPerSample);
|
|
287
290
|
let low = 1;
|
|
288
291
|
let high = samples.length;
|
|
289
292
|
let firstFittingIndex = samples.length;
|
|
293
|
+
const probeIndex = Math.max(1, Math.min(samples.length, estimatedDropCount));
|
|
294
|
+
if (probeIndex > 1) {
|
|
295
|
+
if (serializedHistorySize(samples.slice(probeIndex), today) <= MAX_TOTAL_BYTES) {
|
|
296
|
+
firstFittingIndex = probeIndex;
|
|
297
|
+
high = probeIndex - 1;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
low = probeIndex + 1;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
290
303
|
while (low <= high) {
|
|
291
304
|
const middle = Math.floor((low + high) / 2);
|
|
292
305
|
const candidate = samples.slice(middle);
|
|
@@ -99,7 +99,7 @@ function gitDiffLinesByPath(projectRoot, relativePaths) {
|
|
|
99
99
|
if (uniquePaths.length === 0) {
|
|
100
100
|
return new Map();
|
|
101
101
|
}
|
|
102
|
-
const result = spawnSync('git', ['diff', '--no-ext-diff', '--unified=0', '--', ...uniquePaths], {
|
|
102
|
+
const result = spawnSync('git', ['diff', 'HEAD', '--no-ext-diff', '--unified=0', '--', ...uniquePaths], {
|
|
103
103
|
cwd: projectRoot,
|
|
104
104
|
encoding: 'utf8',
|
|
105
105
|
env: createCommandEnv(projectRoot, { policy: 'minimal', allowlist: [] }),
|
package/package.json
CHANGED
|
@@ -10,8 +10,8 @@ status_values = ["current", "stale", "needs_review", "missing"]
|
|
|
10
10
|
[documents."agents.root"]
|
|
11
11
|
source = "locales/en/AGENTS.md"
|
|
12
12
|
source_locale = "en"
|
|
13
|
-
revision =
|
|
14
|
-
translations.ko = { path = "locales/ko/AGENTS.md", source_revision =
|
|
13
|
+
revision = 15
|
|
14
|
+
translations.ko = { path = "locales/ko/AGENTS.md", source_revision = 15, status = "current" }
|
|
15
15
|
translations.zh = { path = "locales/zh/AGENTS.md", source_revision = 11, status = "needs_review" }
|
|
16
16
|
translations.es = { path = "locales/es/AGENTS.md", source_revision = 11, status = "needs_review" }
|
|
17
17
|
translations.fr = { path = "locales/fr/AGENTS.md", source_revision = 11, status = "needs_review" }
|
|
@@ -40,8 +40,8 @@ translations.hi = { path = "locales/hi/.mustflow/context/PROJECT.md", source_rev
|
|
|
40
40
|
[documents."docs.agent-workflow"]
|
|
41
41
|
source = "locales/en/.mustflow/docs/agent-workflow.md"
|
|
42
42
|
source_locale = "en"
|
|
43
|
-
revision =
|
|
44
|
-
translations.ko = { path = "locales/ko/.mustflow/docs/agent-workflow.md", source_revision =
|
|
43
|
+
revision = 20
|
|
44
|
+
translations.ko = { path = "locales/ko/.mustflow/docs/agent-workflow.md", source_revision = 20, status = "current" }
|
|
45
45
|
translations.zh = { path = "locales/zh/.mustflow/docs/agent-workflow.md", source_revision = 18, status = "needs_review" }
|
|
46
46
|
translations.es = { path = "locales/es/.mustflow/docs/agent-workflow.md", source_revision = 18, status = "needs_review" }
|
|
47
47
|
translations.fr = { path = "locales/fr/.mustflow/docs/agent-workflow.md", source_revision = 18, status = "needs_review" }
|
|
@@ -62,13 +62,13 @@ translations = {}
|
|
|
62
62
|
[documents."skills.index"]
|
|
63
63
|
source = "locales/en/.mustflow/skills/INDEX.md"
|
|
64
64
|
source_locale = "en"
|
|
65
|
-
revision =
|
|
65
|
+
revision = 108
|
|
66
66
|
translations = {}
|
|
67
67
|
|
|
68
68
|
[documents."skill.adapter-boundary"]
|
|
69
69
|
source = "locales/en/.mustflow/skills/adapter-boundary/SKILL.md"
|
|
70
70
|
source_locale = "en"
|
|
71
|
-
revision =
|
|
71
|
+
revision = 12
|
|
72
72
|
translations = {}
|
|
73
73
|
|
|
74
74
|
[documents."skill.artifact-integrity-check"]
|
|
@@ -86,6 +86,18 @@ translations = {}
|
|
|
86
86
|
[documents."skill.api-contract-change"]
|
|
87
87
|
source = "locales/en/.mustflow/skills/api-contract-change/SKILL.md"
|
|
88
88
|
source_locale = "en"
|
|
89
|
+
revision = 2
|
|
90
|
+
translations = {}
|
|
91
|
+
|
|
92
|
+
[documents."skill.backend-reliability-change"]
|
|
93
|
+
source = "locales/en/.mustflow/skills/backend-reliability-change/SKILL.md"
|
|
94
|
+
source_locale = "en"
|
|
95
|
+
revision = 1
|
|
96
|
+
translations = {}
|
|
97
|
+
|
|
98
|
+
[documents."skill.http-delivery-streaming"]
|
|
99
|
+
source = "locales/en/.mustflow/skills/http-delivery-streaming/SKILL.md"
|
|
100
|
+
source_locale = "en"
|
|
89
101
|
revision = 1
|
|
90
102
|
translations = {}
|
|
91
103
|
|
|
@@ -101,6 +113,12 @@ source_locale = "en"
|
|
|
101
113
|
revision = 6
|
|
102
114
|
translations = {}
|
|
103
115
|
|
|
116
|
+
[documents."skill.ai-generated-code-hardening"]
|
|
117
|
+
source = "locales/en/.mustflow/skills/ai-generated-code-hardening/SKILL.md"
|
|
118
|
+
source_locale = "en"
|
|
119
|
+
revision = 1
|
|
120
|
+
translations = {}
|
|
121
|
+
|
|
104
122
|
[documents."skill.contract-sync-check"]
|
|
105
123
|
source = "locales/en/.mustflow/skills/contract-sync-check/SKILL.md"
|
|
106
124
|
source_locale = "en"
|
|
@@ -152,13 +170,13 @@ translations = {}
|
|
|
152
170
|
[documents."skill.dependency-upgrade-review"]
|
|
153
171
|
source = "locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md"
|
|
154
172
|
source_locale = "en"
|
|
155
|
-
revision =
|
|
173
|
+
revision = 3
|
|
156
174
|
translations = {}
|
|
157
175
|
|
|
158
176
|
[documents."skill.version-freshness-check"]
|
|
159
177
|
source = "locales/en/.mustflow/skills/version-freshness-check/SKILL.md"
|
|
160
178
|
source_locale = "en"
|
|
161
|
-
revision =
|
|
179
|
+
revision = 6
|
|
162
180
|
translations = {}
|
|
163
181
|
|
|
164
182
|
[documents."skill.line-ending-hygiene"]
|
|
@@ -173,6 +191,12 @@ source_locale = "en"
|
|
|
173
191
|
revision = 4
|
|
174
192
|
translations = {}
|
|
175
193
|
|
|
194
|
+
[documents."skill.frontend-render-stability"]
|
|
195
|
+
source = "locales/en/.mustflow/skills/frontend-render-stability/SKILL.md"
|
|
196
|
+
source_locale = "en"
|
|
197
|
+
revision = 2
|
|
198
|
+
translations = {}
|
|
199
|
+
|
|
176
200
|
[documents."skill.diff-risk-review"]
|
|
177
201
|
source = "locales/en/.mustflow/skills/diff-risk-review/SKILL.md"
|
|
178
202
|
source_locale = "en"
|
|
@@ -185,6 +209,12 @@ source_locale = "en"
|
|
|
185
209
|
revision = 3
|
|
186
210
|
translations = {}
|
|
187
211
|
|
|
212
|
+
[documents."skill.heuristic-candidate-selection"]
|
|
213
|
+
source = "locales/en/.mustflow/skills/heuristic-candidate-selection/SKILL.md"
|
|
214
|
+
source_locale = "en"
|
|
215
|
+
revision = 1
|
|
216
|
+
translations = {}
|
|
217
|
+
|
|
188
218
|
[documents."skill.astro-code-change"]
|
|
189
219
|
source = "locales/en/.mustflow/skills/astro-code-change/SKILL.md"
|
|
190
220
|
source_locale = "en"
|
|
@@ -194,7 +224,7 @@ translations = {}
|
|
|
194
224
|
[documents."skill.auth-permission-change"]
|
|
195
225
|
source = "locales/en/.mustflow/skills/auth-permission-change/SKILL.md"
|
|
196
226
|
source_locale = "en"
|
|
197
|
-
revision =
|
|
227
|
+
revision = 2
|
|
198
228
|
translations = {}
|
|
199
229
|
|
|
200
230
|
[documents."skill.config-env-change"]
|
|
@@ -206,7 +236,7 @@ translations = {}
|
|
|
206
236
|
[documents."skill.css-code-change"]
|
|
207
237
|
source = "locales/en/.mustflow/skills/css-code-change/SKILL.md"
|
|
208
238
|
source_locale = "en"
|
|
209
|
-
revision =
|
|
239
|
+
revision = 3
|
|
210
240
|
translations = {}
|
|
211
241
|
|
|
212
242
|
[documents."skill.bun-code-change"]
|
|
@@ -248,7 +278,7 @@ translations = {}
|
|
|
248
278
|
[documents."skill.go-code-change"]
|
|
249
279
|
source = "locales/en/.mustflow/skills/go-code-change/SKILL.md"
|
|
250
280
|
source_locale = "en"
|
|
251
|
-
revision =
|
|
281
|
+
revision = 3
|
|
252
282
|
translations = {}
|
|
253
283
|
|
|
254
284
|
[documents."skill.hono-code-change"]
|
|
@@ -260,7 +290,7 @@ translations = {}
|
|
|
260
290
|
[documents."skill.html-code-change"]
|
|
261
291
|
source = "locales/en/.mustflow/skills/html-code-change/SKILL.md"
|
|
262
292
|
source_locale = "en"
|
|
263
|
-
revision =
|
|
293
|
+
revision = 3
|
|
264
294
|
translations = {}
|
|
265
295
|
|
|
266
296
|
[documents."skill.javascript-code-change"]
|
|
@@ -278,13 +308,13 @@ translations = {}
|
|
|
278
308
|
[documents."skill.python-code-change"]
|
|
279
309
|
source = "locales/en/.mustflow/skills/python-code-change/SKILL.md"
|
|
280
310
|
source_locale = "en"
|
|
281
|
-
revision =
|
|
311
|
+
revision = 3
|
|
282
312
|
translations = {}
|
|
283
313
|
|
|
284
314
|
[documents."skill.rust-code-change"]
|
|
285
315
|
source = "locales/en/.mustflow/skills/rust-code-change/SKILL.md"
|
|
286
316
|
source_locale = "en"
|
|
287
|
-
revision =
|
|
317
|
+
revision = 4
|
|
288
318
|
translations = {}
|
|
289
319
|
|
|
290
320
|
[documents."skill.runtime-target-selection"]
|
|
@@ -308,25 +338,31 @@ translations = {}
|
|
|
308
338
|
[documents."skill.tailwind-code-change"]
|
|
309
339
|
source = "locales/en/.mustflow/skills/tailwind-code-change/SKILL.md"
|
|
310
340
|
source_locale = "en"
|
|
311
|
-
revision =
|
|
341
|
+
revision = 3
|
|
312
342
|
translations = {}
|
|
313
343
|
|
|
314
344
|
[documents."skill.tauri-code-change"]
|
|
315
345
|
source = "locales/en/.mustflow/skills/tauri-code-change/SKILL.md"
|
|
316
346
|
source_locale = "en"
|
|
317
|
-
revision =
|
|
347
|
+
revision = 3
|
|
318
348
|
translations = {}
|
|
319
349
|
|
|
320
350
|
[documents."skill.typescript-code-change"]
|
|
321
351
|
source = "locales/en/.mustflow/skills/typescript-code-change/SKILL.md"
|
|
322
352
|
source_locale = "en"
|
|
323
|
-
revision =
|
|
353
|
+
revision = 3
|
|
324
354
|
translations = {}
|
|
325
355
|
|
|
326
356
|
[documents."skill.unocss-code-change"]
|
|
327
357
|
source = "locales/en/.mustflow/skills/unocss-code-change/SKILL.md"
|
|
328
358
|
source_locale = "en"
|
|
329
|
-
revision =
|
|
359
|
+
revision = 3
|
|
360
|
+
translations = {}
|
|
361
|
+
|
|
362
|
+
[documents."skill.service-boundary-architecture"]
|
|
363
|
+
source = "locales/en/.mustflow/skills/service-boundary-architecture/SKILL.md"
|
|
364
|
+
source_locale = "en"
|
|
365
|
+
revision = 1
|
|
330
366
|
translations = {}
|
|
331
367
|
|
|
332
368
|
[documents."skill.cli-output-contract-review"]
|
|
@@ -469,7 +505,7 @@ translations = {}
|
|
|
469
505
|
[documents."skill.performance-budget-check"]
|
|
470
506
|
source = "locales/en/.mustflow/skills/performance-budget-check/SKILL.md"
|
|
471
507
|
source_locale = "en"
|
|
472
|
-
revision =
|
|
508
|
+
revision = 20
|
|
473
509
|
translations = {}
|
|
474
510
|
|
|
475
511
|
[documents."skill.pattern-scout"]
|
|
@@ -478,6 +514,12 @@ source_locale = "en"
|
|
|
478
514
|
revision = 2
|
|
479
515
|
translations = {}
|
|
480
516
|
|
|
517
|
+
[documents."skill.proactive-risk-surfacing"]
|
|
518
|
+
source = "locales/en/.mustflow/skills/proactive-risk-surfacing/SKILL.md"
|
|
519
|
+
source_locale = "en"
|
|
520
|
+
revision = 1
|
|
521
|
+
translations = {}
|
|
522
|
+
|
|
481
523
|
[documents."skill.process-execution-safety"]
|
|
482
524
|
source = "locales/en/.mustflow/skills/process-execution-safety/SKILL.md"
|
|
483
525
|
source_locale = "en"
|
|
@@ -559,13 +601,13 @@ translations = {}
|
|
|
559
601
|
[documents."skill.security-privacy-review"]
|
|
560
602
|
source = "locales/en/.mustflow/skills/security-privacy-review/SKILL.md"
|
|
561
603
|
source_locale = "en"
|
|
562
|
-
revision =
|
|
604
|
+
revision = 22
|
|
563
605
|
translations = {}
|
|
564
606
|
|
|
565
607
|
[documents."skill.security-regression-tests"]
|
|
566
608
|
source = "locales/en/.mustflow/skills/security-regression-tests/SKILL.md"
|
|
567
609
|
source_locale = "en"
|
|
568
|
-
revision =
|
|
610
|
+
revision = 12
|
|
569
611
|
translations = {}
|
|
570
612
|
|
|
571
613
|
[documents."skill.search-ad-content-authoring"]
|