mustflow 2.115.3 → 2.115.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/dist/cli/lib/local-index/constants.js +1 -1
- package/dist/cli/lib/local-index/freshness.js +65 -35
- package/dist/cli/lib/local-index/index.js +26 -9
- package/dist/cli/lib/local-index/populate.js +4 -1
- package/dist/cli/lib/local-index/schema.js +10 -0
- package/dist/cli/lib/local-index/source-index.js +43 -11
- package/package.json +1 -1
- package/templates/default/i18n.toml +2 -2
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -2
- package/templates/default/locales/en/.mustflow/skills/release-publish-change/SKILL.md +13 -5
- package/templates/default/manifest.toml +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const LOCAL_INDEX_SCHEMA_VERSION = '
|
|
1
|
+
export const LOCAL_INDEX_SCHEMA_VERSION = '21';
|
|
2
2
|
export const LOCAL_INDEX_PARSER_VERSION = '1';
|
|
3
3
|
export const DEFAULT_DATABASE_RELATIVE_PATH = '.mustflow/cache/mustflow.sqlite';
|
|
4
4
|
export const LATEST_RUN_STATE_RELATIVE_PATH = '.mustflow/state/runs/latest.json';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { LOCAL_INDEX_SCHEMA_VERSION } from './constants.js';
|
|
1
|
+
import { DEFAULT_DATABASE_RELATIVE_PATH, LOCAL_INDEX_SCHEMA_VERSION } from './constants.js';
|
|
2
2
|
import { hasTable, queryRows, readMetadataValue, toSearchString } from './database-read.js';
|
|
3
|
-
import { normalizeIndexedFileSourceScope, readIndexedFileRecord } from './source-index.js';
|
|
3
|
+
import { collectSourceAnchorCandidatePaths, getSourceScopeHash, normalizeIndexedFileSourceScope, readIndexedFileRecord, readLocalIndexSourceConfig, } from './source-index.js';
|
|
4
4
|
import { collectDocuments } from './workflow-documents.js';
|
|
5
5
|
export function readStoredSchemaVersion(database) {
|
|
6
6
|
return readMetadataValue(database, 'schema_version');
|
|
@@ -11,46 +11,76 @@ export function getStalePaths(projectRoot, database, options = {}) {
|
|
|
11
11
|
if (schemaVersion !== LOCAL_INDEX_SCHEMA_VERSION) {
|
|
12
12
|
return ['.mustflow/cache/mustflow.sqlite'];
|
|
13
13
|
}
|
|
14
|
-
if (hasTable(database, 'indexed_files')
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
if (!hasTable(database, 'indexed_files') ||
|
|
15
|
+
!hasTable(database, 'indexed_source_candidates') ||
|
|
16
|
+
!hasTable(database, 'source_anchors') ||
|
|
17
|
+
!hasTable(database, 'source_anchor_status')) {
|
|
18
|
+
return [DEFAULT_DATABASE_RELATIVE_PATH];
|
|
19
|
+
}
|
|
20
|
+
const stalePaths = new Set();
|
|
21
|
+
const indexedRows = queryRows(database, 'SELECT path, source_scope, content_hash FROM indexed_files');
|
|
22
|
+
const indexedPaths = new Set(indexedRows.map((row) => toSearchString(row.path)));
|
|
23
|
+
const indexedSourcePaths = new Set(queryRows(database, 'SELECT path FROM indexed_source_candidates').map((row) => toSearchString(row.path)));
|
|
24
|
+
for (const sourcePath of indexedSourcePaths) {
|
|
25
|
+
if (!indexedPaths.has(sourcePath)) {
|
|
26
|
+
stalePaths.add(sourcePath);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const row of indexedRows) {
|
|
30
|
+
const indexedPath = toSearchString(row.path);
|
|
31
|
+
const sourceScope = normalizeIndexedFileSourceScope(toSearchString(row.source_scope));
|
|
32
|
+
if (!includeState && sourceScope === 'state') {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const current = readIndexedFileRecord(projectRoot, indexedPath, sourceScope);
|
|
37
|
+
if (current.contentHash !== toSearchString(row.content_hash)) {
|
|
31
38
|
stalePaths.add(indexedPath);
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
stalePaths.add(document.path);
|
|
37
|
-
}
|
|
41
|
+
catch {
|
|
42
|
+
stalePaths.add(indexedPath);
|
|
38
43
|
}
|
|
39
|
-
return Array.from(stalePaths).sort((left, right) => left.localeCompare(right));
|
|
40
44
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const currentHashes = new Map(currentDocuments.map((document) => [document.path, document.contentHash]));
|
|
45
|
-
const stalePaths = new Set();
|
|
46
|
-
for (const [indexedPath, indexedHash] of indexedHashes) {
|
|
47
|
-
if (currentHashes.get(indexedPath) !== indexedHash) {
|
|
48
|
-
stalePaths.add(indexedPath);
|
|
45
|
+
for (const document of collectDocuments(projectRoot)) {
|
|
46
|
+
if (!indexedPaths.has(document.path)) {
|
|
47
|
+
stalePaths.add(document.path);
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
const sourceIndexEnabledValue = readMetadataValue(database, 'source_index_enabled');
|
|
51
|
+
if (sourceIndexEnabledValue !== 'true' && sourceIndexEnabledValue !== 'false') {
|
|
52
|
+
stalePaths.add(DEFAULT_DATABASE_RELATIVE_PATH);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const sourceIndexEnabled = sourceIndexEnabledValue === 'true';
|
|
56
|
+
const hasIndexedSourceScope = indexedRows.some((row) => normalizeIndexedFileSourceScope(toSearchString(row.source_scope)) === 'source_anchor');
|
|
57
|
+
const hasSourceAnchorRows = hasTable(database, 'source_anchors') && queryRows(database, 'SELECT id FROM source_anchors LIMIT 1').length > 0;
|
|
58
|
+
if (!sourceIndexEnabled && (indexedSourcePaths.size > 0 || hasIndexedSourceScope || hasSourceAnchorRows)) {
|
|
59
|
+
stalePaths.add(DEFAULT_DATABASE_RELATIVE_PATH);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const sourceConfig = readLocalIndexSourceConfig(projectRoot);
|
|
63
|
+
const storedSourceScopeHash = readMetadataValue(database, 'source_scope_hash');
|
|
64
|
+
const currentSourceScopeHash = getSourceScopeHash(sourceIndexEnabled, sourceConfig);
|
|
65
|
+
if (storedSourceScopeHash !== currentSourceScopeHash) {
|
|
66
|
+
stalePaths.add(DEFAULT_DATABASE_RELATIVE_PATH);
|
|
67
|
+
}
|
|
68
|
+
if (sourceIndexEnabled) {
|
|
69
|
+
const currentSourcePaths = new Set(collectSourceAnchorCandidatePaths(projectRoot, sourceConfig));
|
|
70
|
+
for (const sourcePath of currentSourcePaths) {
|
|
71
|
+
if (!indexedSourcePaths.has(sourcePath)) {
|
|
72
|
+
stalePaths.add(sourcePath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const sourcePath of indexedSourcePaths) {
|
|
76
|
+
if (!currentSourcePaths.has(sourcePath)) {
|
|
77
|
+
stalePaths.add(sourcePath);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
stalePaths.add(DEFAULT_DATABASE_RELATIVE_PATH);
|
|
54
84
|
}
|
|
55
85
|
}
|
|
56
86
|
return Array.from(stalePaths).sort((left, right) => left.localeCompare(right));
|
|
@@ -158,6 +158,14 @@ function indexedFilesMatch(database, currentFiles) {
|
|
|
158
158
|
}
|
|
159
159
|
return true;
|
|
160
160
|
}
|
|
161
|
+
function indexedSourceCandidatesMatch(database, currentPaths) {
|
|
162
|
+
if (!hasTable(database, 'indexed_source_candidates')) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
const storedPaths = new Set(queryRows(database, 'SELECT path FROM indexed_source_candidates').map((row) => toSearchString(row.path)));
|
|
166
|
+
const currentPathSet = new Set(currentPaths);
|
|
167
|
+
return storedPaths.size === currentPathSet.size && [...storedPaths].every((storedPath) => currentPathSet.has(storedPath));
|
|
168
|
+
}
|
|
161
169
|
const INDEXED_FILE_MTIME_TOLERANCE_MS = 1;
|
|
162
170
|
function indexedFileMtimeMsEqual(storedMtimeMs, currentMtimeMs) {
|
|
163
171
|
return storedMtimeMs !== null && Math.abs(storedMtimeMs - currentMtimeMs) <= INDEXED_FILE_MTIME_TOLERANCE_MS;
|
|
@@ -183,8 +191,8 @@ function indexedFileMetadataMatch(database, currentFiles) {
|
|
|
183
191
|
}
|
|
184
192
|
return true;
|
|
185
193
|
}
|
|
186
|
-
async function readIncrementalPreflightReuse(SQL, databasePath, projectRoot, currentFiles, sourceScopeHash, dryRun, indexMode) {
|
|
187
|
-
if (!currentFiles) {
|
|
194
|
+
async function readIncrementalPreflightReuse(SQL, databasePath, projectRoot, currentFiles, currentSourceCandidatePaths, sourceScopeHash, dryRun, indexMode) {
|
|
195
|
+
if (!currentFiles || !currentSourceCandidatePaths) {
|
|
188
196
|
return { result: null, rebuildReason: null };
|
|
189
197
|
}
|
|
190
198
|
if (!existsSync(databasePath)) {
|
|
@@ -205,6 +213,9 @@ async function readIncrementalPreflightReuse(SQL, databasePath, projectRoot, cur
|
|
|
205
213
|
if (!hasTable(database, 'indexed_files')) {
|
|
206
214
|
return { result: null, rebuildReason: 'indexed_files_missing' };
|
|
207
215
|
}
|
|
216
|
+
if (!indexedSourceCandidatesMatch(database, currentSourceCandidatePaths)) {
|
|
217
|
+
return { result: null, rebuildReason: 'source_scope_mismatch' };
|
|
218
|
+
}
|
|
208
219
|
if (!indexedFileMetadataMatch(database, currentFiles)) {
|
|
209
220
|
return { result: null, rebuildReason: 'file_fingerprint_mismatch' };
|
|
210
221
|
}
|
|
@@ -225,7 +236,7 @@ async function readIncrementalPreflightReuse(SQL, databasePath, projectRoot, cur
|
|
|
225
236
|
database?.close();
|
|
226
237
|
}
|
|
227
238
|
}
|
|
228
|
-
async function readIncrementalReuseDecision(SQL, databasePath, currentFiles, sourceScopeHash) {
|
|
239
|
+
async function readIncrementalReuseDecision(SQL, databasePath, currentFiles, currentSourceCandidatePaths, sourceScopeHash) {
|
|
229
240
|
if (!existsSync(databasePath)) {
|
|
230
241
|
return { reusable: false, rebuildReason: 'missing_index', capabilities: null };
|
|
231
242
|
}
|
|
@@ -244,6 +255,9 @@ async function readIncrementalReuseDecision(SQL, databasePath, currentFiles, sou
|
|
|
244
255
|
if (!hasTable(database, 'indexed_files')) {
|
|
245
256
|
return { reusable: false, rebuildReason: 'indexed_files_missing', capabilities: null };
|
|
246
257
|
}
|
|
258
|
+
if (!indexedSourceCandidatesMatch(database, currentSourceCandidatePaths)) {
|
|
259
|
+
return { reusable: false, rebuildReason: 'source_scope_mismatch', capabilities: null };
|
|
260
|
+
}
|
|
247
261
|
if (!indexedFilesMatch(database, currentFiles)) {
|
|
248
262
|
return { reusable: false, rebuildReason: 'file_fingerprint_mismatch', capabilities: null };
|
|
249
263
|
}
|
|
@@ -284,7 +298,7 @@ export async function createLocalIndex(projectRoot, options = {}) {
|
|
|
284
298
|
capabilityDatabase.close();
|
|
285
299
|
if (incremental) {
|
|
286
300
|
const preflightFiles = collectFastPreflightIndexedFileMetadataRecords(projectRoot, includeSource, sourceConfig);
|
|
287
|
-
const preflightReuse = await readIncrementalPreflightReuse(SQL, databasePath, projectRoot, preflightFiles, sourceScopeHash, dryRun, indexMode);
|
|
301
|
+
const preflightReuse = await readIncrementalPreflightReuse(SQL, databasePath, projectRoot, preflightFiles?.records ?? null, preflightFiles?.sourceCandidatePaths ?? null, sourceScopeHash, dryRun, indexMode);
|
|
288
302
|
if (preflightReuse.result) {
|
|
289
303
|
return preflightReuse.result;
|
|
290
304
|
}
|
|
@@ -307,16 +321,16 @@ export async function createLocalIndex(projectRoot, options = {}) {
|
|
|
307
321
|
const verificationEvidence = createVerificationEvidenceIndex(projectRoot);
|
|
308
322
|
const indexedFiles = collectIndexedFileRecords(projectRoot, documents, sourceAnchors, sourceAnchorCandidatePaths);
|
|
309
323
|
if (incremental) {
|
|
310
|
-
const reuseDecision = await readIncrementalReuseDecision(SQL, databasePath, indexedFiles, sourceScopeHash);
|
|
324
|
+
const reuseDecision = await readIncrementalReuseDecision(SQL, databasePath, indexedFiles, sourceAnchorCandidatePaths, sourceScopeHash);
|
|
311
325
|
reusedExisting = reuseDecision.reusable;
|
|
312
|
-
rebuildReason = reuseDecision.rebuildReason ?? rebuildReason;
|
|
326
|
+
rebuildReason = reuseDecision.reusable ? null : (reuseDecision.rebuildReason ?? rebuildReason);
|
|
313
327
|
capabilities = reuseDecision.capabilities ?? capabilities;
|
|
314
328
|
}
|
|
315
329
|
if (!dryRun && !reusedExisting) {
|
|
316
330
|
const database = new SQL.Database();
|
|
317
331
|
try {
|
|
318
332
|
createSchema(database, capabilities);
|
|
319
|
-
populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, verificationEvidence, indexMode, sourceScopeHash, includeSource, new Date().toISOString());
|
|
333
|
+
populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, sourceAnchorCandidatePaths, verificationEvidence, indexMode, sourceScopeHash, includeSource, new Date().toISOString());
|
|
320
334
|
writeFileInsideWithoutSymlinks(projectRoot, databasePath, database.export());
|
|
321
335
|
}
|
|
322
336
|
finally {
|
|
@@ -702,10 +716,13 @@ export async function readLocalSourceAnchorCheckWarnings(projectRoot) {
|
|
|
702
716
|
if (readMetadataValue(database, 'source_index_enabled') !== 'true') {
|
|
703
717
|
return [];
|
|
704
718
|
}
|
|
705
|
-
if (!hasTable(database, 'indexed_files') ||
|
|
719
|
+
if (!hasTable(database, 'indexed_files') ||
|
|
720
|
+
!hasTable(database, 'indexed_source_candidates') ||
|
|
721
|
+
!hasTable(database, 'source_anchors') ||
|
|
722
|
+
!hasTable(database, 'source_anchor_status')) {
|
|
706
723
|
return ['source anchor local index is missing source-anchor tables; refresh with mf index --source'];
|
|
707
724
|
}
|
|
708
|
-
const indexedSourcePaths = new Set(queryRows(database, 'SELECT path FROM
|
|
725
|
+
const indexedSourcePaths = new Set(queryRows(database, 'SELECT path FROM indexed_source_candidates')
|
|
709
726
|
.map((row) => toSearchString(row.path))
|
|
710
727
|
.filter(Boolean));
|
|
711
728
|
const staleSourcePaths = getStalePaths(projectRoot, database, { includeState: false }).filter((stalePath) => indexedSourcePaths.has(stalePath));
|
|
@@ -172,7 +172,7 @@ function rollbackTransaction(database) {
|
|
|
172
172
|
// Keep the original indexing failure as the actionable error.
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
-
export function populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, verificationEvidence, indexMode, sourceScopeHash, sourceIndexEnabled, indexedAt) {
|
|
175
|
+
export function populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, indexedSourceCandidates, verificationEvidence, indexMode, sourceScopeHash, sourceIndexEnabled, indexedAt) {
|
|
176
176
|
const sourceAnchorRiskSignals = createSourceAnchorRiskSignals(sourceAnchors);
|
|
177
177
|
database.run('BEGIN TRANSACTION');
|
|
178
178
|
try {
|
|
@@ -223,6 +223,9 @@ export function populateDatabase(database, capabilities, documents, skills, skil
|
|
|
223
223
|
LOCAL_INDEX_PARSER_VERSION,
|
|
224
224
|
]);
|
|
225
225
|
}
|
|
226
|
+
for (const sourceCandidatePath of indexedSourceCandidates) {
|
|
227
|
+
database.run('INSERT INTO indexed_source_candidates (path) VALUES (?)', [sourceCandidatePath]);
|
|
228
|
+
}
|
|
226
229
|
for (const document of documents) {
|
|
227
230
|
database.run('INSERT INTO documents (path, type, title, locale, revision, content_hash, content_snippet) VALUES (?, ?, ?, ?, ?, ?, ?)', [
|
|
228
231
|
document.path,
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { SEARCH_BACKEND_FTS5 } from './constants.js';
|
|
2
2
|
export function createSchema(database, capabilities) {
|
|
3
|
+
database.run('PRAGMA foreign_keys = ON');
|
|
4
|
+
const [foreignKeyResult] = database.exec('PRAGMA foreign_keys');
|
|
5
|
+
if (foreignKeyResult?.values[0]?.[0] !== 1) {
|
|
6
|
+
throw new Error('SQLite foreign-key enforcement is unavailable for the local index.');
|
|
7
|
+
}
|
|
3
8
|
database.run(`
|
|
4
9
|
CREATE TABLE metadata (
|
|
5
10
|
key TEXT PRIMARY KEY,
|
|
@@ -17,6 +22,11 @@ CREATE TABLE indexed_files (
|
|
|
17
22
|
parser_version TEXT NOT NULL
|
|
18
23
|
);
|
|
19
24
|
|
|
25
|
+
CREATE TABLE indexed_source_candidates (
|
|
26
|
+
path TEXT PRIMARY KEY,
|
|
27
|
+
FOREIGN KEY (path) REFERENCES indexed_files(path) ON UPDATE RESTRICT ON DELETE RESTRICT
|
|
28
|
+
);
|
|
29
|
+
|
|
20
30
|
CREATE TABLE documents (
|
|
21
31
|
path TEXT PRIMARY KEY,
|
|
22
32
|
type TEXT NOT NULL,
|
|
@@ -1,11 +1,34 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isRecord, readStringArray } from '../command-contract.js';
|
|
4
|
+
import { MUSTFLOW_TEXT_MAX_BYTES, mustflowProjectPath } from '../mustflow-read.js';
|
|
4
5
|
import { readMustflowTomlFile } from '../toml.js';
|
|
6
|
+
import { ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from '../../../core/safe-filesystem.js';
|
|
5
7
|
import { listSourceAnchorFiles } from '../../../core/source-anchors.js';
|
|
6
8
|
import { INDEX_CONFIG_RELATIVE_PATH, LATEST_RUN_STATE_RELATIVE_PATH, MUSTFLOW_RELATIVE_PATH, SOURCE_INDEX_MAX_FILE_BYTES, } from './constants.js';
|
|
7
9
|
import { sha256Bytes, sha256Text } from './hashing.js';
|
|
8
10
|
import { getExistingIndexablePaths } from './workflow-documents.js';
|
|
11
|
+
function indexedProjectPath(projectRoot, relativePath) {
|
|
12
|
+
if (relativePath.length === 0 ||
|
|
13
|
+
relativePath.includes('\0') ||
|
|
14
|
+
relativePath.includes('\\') ||
|
|
15
|
+
path.posix.isAbsolute(relativePath) ||
|
|
16
|
+
path.win32.isAbsolute(relativePath)) {
|
|
17
|
+
throw new Error(`Indexed file path must be a canonical project-relative path: ${relativePath}`);
|
|
18
|
+
}
|
|
19
|
+
const segments = relativePath.split('/');
|
|
20
|
+
if (segments.some((segment) => segment.length === 0 || segment === '.' || segment === '..')) {
|
|
21
|
+
throw new Error(`Indexed file path must be a canonical project-relative path: ${relativePath}`);
|
|
22
|
+
}
|
|
23
|
+
const fullPath = mustflowProjectPath(projectRoot, relativePath);
|
|
24
|
+
ensureInsideWithoutSymlinks(projectRoot, fullPath);
|
|
25
|
+
return fullPath;
|
|
26
|
+
}
|
|
27
|
+
function readIndexedFileBytes(projectRoot, relativePath) {
|
|
28
|
+
return readFileInsideWithoutSymlinks(projectRoot, indexedProjectPath(projectRoot, relativePath), {
|
|
29
|
+
maxBytes: MUSTFLOW_TEXT_MAX_BYTES,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
9
32
|
function readIndexToml(projectRoot) {
|
|
10
33
|
const indexConfigPath = path.join(projectRoot, ...INDEX_CONFIG_RELATIVE_PATH.split('/'));
|
|
11
34
|
if (!existsSync(indexConfigPath)) {
|
|
@@ -69,13 +92,13 @@ export function readIndexedFileRecord(projectRoot, relativePath, sourceScope, co
|
|
|
69
92
|
const metadata = readIndexedFileMetadataRecord(projectRoot, relativePath, sourceScope);
|
|
70
93
|
return {
|
|
71
94
|
...metadata,
|
|
72
|
-
contentHash: contentHash ?? sha256Bytes(
|
|
95
|
+
contentHash: contentHash ?? sha256Bytes(readIndexedFileBytes(projectRoot, relativePath)),
|
|
73
96
|
};
|
|
74
97
|
}
|
|
75
98
|
function hashIndexedFileMetadataRecord(projectRoot, metadata) {
|
|
76
99
|
return {
|
|
77
100
|
...metadata,
|
|
78
|
-
contentHash: sha256Bytes(
|
|
101
|
+
contentHash: sha256Bytes(readIndexedFileBytes(projectRoot, metadata.path)),
|
|
79
102
|
};
|
|
80
103
|
}
|
|
81
104
|
function tryHashIndexedFileMetadataRecord(projectRoot, metadata) {
|
|
@@ -92,8 +115,11 @@ export function hashIndexedFileMetadataRecords(projectRoot, metadataRecords) {
|
|
|
92
115
|
.filter((record) => Boolean(record));
|
|
93
116
|
}
|
|
94
117
|
export function readIndexedFileMetadataRecord(projectRoot, relativePath, sourceScope) {
|
|
95
|
-
const fullPath =
|
|
118
|
+
const fullPath = indexedProjectPath(projectRoot, relativePath);
|
|
96
119
|
const stats = statSync(fullPath);
|
|
120
|
+
if (!stats.isFile()) {
|
|
121
|
+
throw new Error(`Indexed path must be a regular file: ${relativePath}`);
|
|
122
|
+
}
|
|
97
123
|
return {
|
|
98
124
|
path: relativePath,
|
|
99
125
|
sourceScope,
|
|
@@ -117,10 +143,7 @@ export function collectIndexedFileRecords(projectRoot, documents, sourceAnchors,
|
|
|
117
143
|
const sourcePaths = new Set([...sourceAnchorCandidatePaths, ...sourceAnchors.map((anchor) => anchor.path)]);
|
|
118
144
|
for (const anchorPath of [...sourcePaths].sort((left, right) => left.localeCompare(right))) {
|
|
119
145
|
if (!records.has(anchorPath)) {
|
|
120
|
-
|
|
121
|
-
if (record) {
|
|
122
|
-
records.set(anchorPath, record);
|
|
123
|
-
}
|
|
146
|
+
records.set(anchorPath, readIndexedFileRecord(projectRoot, anchorPath, 'source_anchor'));
|
|
124
147
|
}
|
|
125
148
|
}
|
|
126
149
|
if (existsSync(path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/')))) {
|
|
@@ -132,19 +155,25 @@ export function collectIndexedFileRecords(projectRoot, documents, sourceAnchors,
|
|
|
132
155
|
return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
133
156
|
}
|
|
134
157
|
export function collectSourceAnchorCandidatePaths(projectRoot, sourceConfig) {
|
|
135
|
-
|
|
158
|
+
const paths = listSourceAnchorFiles(projectRoot, {
|
|
136
159
|
...sourceConfig,
|
|
137
160
|
excludeGeneratedOrVendor: true,
|
|
138
161
|
});
|
|
162
|
+
for (const relativePath of paths) {
|
|
163
|
+
indexedProjectPath(projectRoot, relativePath);
|
|
164
|
+
}
|
|
165
|
+
return paths;
|
|
139
166
|
}
|
|
140
167
|
export function collectFastPreflightIndexedFileMetadataRecords(projectRoot, includeSource, sourceConfig) {
|
|
141
168
|
const records = new Map();
|
|
169
|
+
let sourceCandidatePaths = [];
|
|
142
170
|
for (const relativePath of getExistingIndexablePaths(projectRoot)) {
|
|
143
171
|
records.set(relativePath, readIndexedFileMetadataRecord(projectRoot, relativePath, 'workflow'));
|
|
144
172
|
}
|
|
145
173
|
if (includeSource) {
|
|
146
174
|
try {
|
|
147
|
-
|
|
175
|
+
sourceCandidatePaths = collectSourceAnchorCandidatePaths(projectRoot, sourceConfig);
|
|
176
|
+
for (const sourcePath of sourceCandidatePaths) {
|
|
148
177
|
if (!records.has(sourcePath)) {
|
|
149
178
|
records.set(sourcePath, readIndexedFileMetadataRecord(projectRoot, sourcePath, 'source_anchor'));
|
|
150
179
|
}
|
|
@@ -157,5 +186,8 @@ export function collectFastPreflightIndexedFileMetadataRecords(projectRoot, incl
|
|
|
157
186
|
if (existsSync(path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/')))) {
|
|
158
187
|
records.set(LATEST_RUN_STATE_RELATIVE_PATH, readIndexedFileMetadataRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH, 'state'));
|
|
159
188
|
}
|
|
160
|
-
return
|
|
189
|
+
return {
|
|
190
|
+
records: [...records.values()].sort((left, right) => left.path.localeCompare(right.path)),
|
|
191
|
+
sourceCandidatePaths,
|
|
192
|
+
};
|
|
161
193
|
}
|
package/package.json
CHANGED
|
@@ -62,7 +62,7 @@ translations = {}
|
|
|
62
62
|
[documents."skills.index"]
|
|
63
63
|
source = "locales/en/.mustflow/skills/INDEX.md"
|
|
64
64
|
source_locale = "en"
|
|
65
|
-
revision =
|
|
65
|
+
revision = 229
|
|
66
66
|
translations = {}
|
|
67
67
|
|
|
68
68
|
[documents."skill.adapter-boundary"]
|
|
@@ -1164,7 +1164,7 @@ translations = {}
|
|
|
1164
1164
|
[documents."skill.release-publish-change"]
|
|
1165
1165
|
source = "locales/en/.mustflow/skills/release-publish-change/SKILL.md"
|
|
1166
1166
|
source_locale = "en"
|
|
1167
|
-
revision =
|
|
1167
|
+
revision = 3
|
|
1168
1168
|
translations = {}
|
|
1169
1169
|
|
|
1170
1170
|
[documents."skill.security-privacy-review"]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
mustflow_doc: skills.index
|
|
3
3
|
locale: en
|
|
4
4
|
canonical: true
|
|
5
|
-
revision:
|
|
5
|
+
revision: 229
|
|
6
6
|
authority: router
|
|
7
7
|
lifecycle: mustflow-owned
|
|
8
8
|
---
|
|
@@ -632,7 +632,7 @@ routes. Event routes stay inactive until their event occurs.
|
|
|
632
632
|
| `README.md` is created, restructured, or substantially rewritten | `.mustflow/skills/readme-authoring/SKILL.md` | User request, existing README if any, repository evidence, nearest instructions, and command contracts | `README.md` and directly linked public docs | invented project claims, marketing drift, or loss of human-authored intent | `docs_validate_fast`, `mustflow_check` | Evidence-based README changes, preserved or deferred sections, verification notes |
|
|
633
633
|
| README content is generated, reviewed, or audited for unsupported AI-generated claims, fake commands, invented environment variables, README-as-investment-deck overclaim, unshipped roadmap-as-current wording, fake or internal-demo usage examples, TODO/temporary/internal-state wording, executable install/run/test/build contracts, pasteable examples, public API examples, security exposure, performance, platform support, license, trademark, copyright, credit, attribution, roadmap, architecture, or file-tree explanations | `.mustflow/skills/readme-evidence-gate/SKILL.md` | README draft or sections, evidence ledger, repository files, package manager metadata, lockfiles, `bin`, `exports`, public exports, command sources, CLI parser/help/output evidence, config/env sources, support and badge evidence, tests, license and notice files, SPDX identifiers, source headers, package metadata, contributor and trademark evidence, CI matrices, Dockerfiles, benchmarks, maintained docs, screenshots, `.env.example`, code-block execution policy, clean-environment evidence, secret-scanning policy, and external or AI source text | README wording and directly synchronized README examples, docs links, placeholder examples, code-block labels, legal/credit wording, or screenshot guidance | hallucinated README contract, fake quick-start, invented env/API, subjective maintenance-debt prose, unsupported pasteable snippet, untested executable README contract, OS-shell mismatch, missing server readiness, unsupported security/performance/platform/license/trademark claim, mixed roadmap/current support, hidden internal demo prerequisite, unsupported badge or support claim, real-looking fake key, API key in URL query, internal URL, private IP, production path, pasteable production command, unsafe screenshot, or speculative file tree | `docs_validate_fast`, `test_related`, `test_release`, `mustflow_check` | README evidence ledger, claims kept/qualified/moved/removed, subjective phrases converted, overclaim/support/badge/failure-path checks, executable-command and clean-environment checks, pasteable contract checks, legal/SPDX/NOTICE/trademark/credit checks, security exposure checks, unsupported claims found, verification notes, and remaining README evidence risk |
|
|
634
634
|
| Release notes, changelog entries, public change summaries, release preparation copy, or package release wording are drafted or revised | `.mustflow/skills/release-notes-authoring/SKILL.md` | User-provided change summary, current diff summary, release audience, public surfaces, version source, and command contract entries | Release notes, changelog entries, release preparation notes, and directly synchronized docs or package metadata | invented release history, inflated public claims, internal noise, stale version or migration notes, or unverified release evidence | `changes_status`, `changes_diff_summary`, `docs_validate_fast`, `test_release`, `mustflow_check` | Release audience, categorized notes, excluded internal changes, version or migration checks, verification, skipped release-history checks, and remaining release-note risk |
|
|
635
|
-
| Release publishing, package registry publication, remote release channels, Git tags, GitHub Releases, release assets, npm, PyPI, crates.io, Go modules, Docker images, Homebrew formulae or casks, app updater metadata, version bump decisions, artifact inspection, post-publish smoke tests, rollback or yanking plans, or user installation paths are created, changed, reviewed, or reported | `.mustflow/skills/release-publish-change/SKILL.md` | Release target, version, channel, package name, module path, image name, tag, artifact names, expected assets, public contract source, artifact inspection method, remote publication surface, recovery model, and command contract entries | Version metadata, release workflows, package manifests, artifact manifests, changelog or release-preparation docs, package tests, install-smoke expectations, release validation tests, and installed-template metadata | local-only release claim, wrong version bump, stale artifact, registry overwrite assumption, missing asset, bad checksum or signature, moved Go tag, unverified Docker digest, updater metadata breakage, missing user-path smoke test, or false rollback claim | `changes_status`, `changes_diff_summary`, `lint`, `build`, `test_related`, `test`, `docs_validate_fast`, `test_release`, `mustflow_check` | Release target, version and channel, public API classification, artifact inspection evidence, remote publication state, user-path smoke
|
|
635
|
+
| Release publishing, package registry publication, remote release channels, Git tags, GitHub Releases, release assets, npm, PyPI, crates.io, Go modules, Docker images, Homebrew formulae or casks, app updater metadata, version bump decisions, artifact inspection, post-publish smoke tests, rollback or yanking plans, or user installation paths are created, changed, reviewed, or reported | `.mustflow/skills/release-publish-change/SKILL.md` | Release target, exact immutable version, channel, package name, module path, image name, tag, artifact names, expected assets, public contract source, artifact inspection method, remote publication surface, consumer environment, public entrypoints, recovery model, and command contract entries | Version metadata, release workflows, package manifests, artifact manifests, changelog or release-preparation docs, package tests, install-smoke expectations, release validation tests, and installed-template metadata | local-only release claim, wrong version bump, stale artifact, cache-only or internal-entrypoint-only smoke, registry overwrite assumption, missing asset, bad checksum or signature, moved Go tag, unverified Docker digest, updater metadata breakage, missing user-path smoke test, or false rollback claim | `changes_status`, `changes_diff_summary`, `lint`, `build`, `test_related`, `test`, `docs_validate_fast`, `test_release`, `mustflow_check` | Release target, exact version and channel, public API classification, artifact inspection evidence, remote publication state, public-entrypoint user-path smoke and environment evidence, synchronized surfaces, recovery classification, verification, and remaining release-publish risk |
|
|
636
636
|
| Search-friendly ad-supported articles, blog posts, guides, reviews, comparisons, FAQs, or evergreen content are planned, written, edited, reviewed, or reported | `.mustflow/skills/search-ad-content-authoring/SKILL.md` | Search intent, reader task, content type, source freshness needs, monetization constraints, article draft or outline, and command contract entries | Article outlines, headings, paragraphs, tables, lists, FAQs, images, links, disclosures, content docs, templates, tests, and reports | keyword stuffing, thin filler, misleading ad adjacency, stale policy or ranking claims, unsupported revenue claims, accessibility or layout instability, or copied competitor content | `changes_status`, `changes_diff_summary`, `docs_validate_fast`, `test_release`, `mustflow_check` | Search intent, outline shape, content structure checks, source freshness, ad layout and trust checks, omitted or verified claims, verification, and remaining content risk |
|
|
637
637
|
| Documentation review queue entries or selected docs need prose cleanup for LLM-like wording, AI-slop signals, low-specificity boilerplate, literal translation, unnatural tone, Korean technical translationese, or domain-term drift | `.mustflow/skills/docs-prose-review/SKILL.md` | Review queue entry or selected document path, review comment if present, target language, audience or genre, domain terminology, reviewer metadata | Selected documentation file and review ledger entry | meaning drift, fake authorship attribution, invented evidence, over-editing, or stale queue state | `docs_validate`, `mustflow_check` | Prose issues fixed, preserved technical meaning, recorded review status, verification notes |
|
|
638
638
|
| Korean or English prose is supplied to extract reusable elegant wording candidates, store selected modular phrase fragments, polish prose with a curated phrase bank, or improve wording for report-style answers, final reports, GitHub issue bodies, pull request descriptions, review replies, maintainer-facing comments, release or update notes, documentation prose, summaries, or explanatory writing after the facts are established | `.mustflow/skills/writing-elegance/SKILL.md` | Source text, mode, target register, target surface, user keep or reject choices, current phrase bank when storing or applying expressions, owning workflow skill when evidence or repository policy matters, and command contract entries when files change | Candidate tables, selected phrase-bank entries, polished report or GitHub wording, `references/phrase-bank.md`, synchronized template copy, route metadata, template manifest, i18n metadata, and directly tied tests | over-specific sentence capture, proper-name leakage, private detail storage, ornamental wording, technical meaning drift, docs-prose-review overlap, GitHub quality-gate overlap, release-note authority drift, completion-evidence drift, phrase-bank bloat, or skipped template sync | `changes_status`, `changes_diff_summary`, `docs_validate_fast`, `test_release`, `mustflow_check` | Mode, target surface, candidate table or stored entries, entries kept or rejected, phrase-bank updates or polish boundary, owning skill applied or deferred, template sync, verification, and remaining style or specificity risk |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
mustflow_doc: skill.release-publish-change
|
|
3
3
|
locale: en
|
|
4
4
|
canonical: true
|
|
5
|
-
revision:
|
|
5
|
+
revision: 3
|
|
6
6
|
lifecycle: mustflow-owned
|
|
7
7
|
authority: procedure
|
|
8
8
|
name: release-publish-change
|
|
@@ -60,7 +60,7 @@ The release is not done when tests pass locally, a version string changes, or a
|
|
|
60
60
|
and whether each belongs to publication, branch CI, tag CI, release asset generation, or another
|
|
61
61
|
independent verification path.
|
|
62
62
|
- Recovery model: unpublish, yank, deprecate, republish with new version, move channel pointer, revoke asset, restore from backup, or forward fix.
|
|
63
|
-
- Configured command intents for build, package inspection, release verification, docs validation, and user installation or updater smoke test. If no such intent exists, report the missing intent instead of inventing a raw command.
|
|
63
|
+
- Configured command intents for build, package inspection, release verification, docs validation, and user installation or updater smoke test. For post-publish checks, also identify the exact immutable version, intended registry or channel, consumer environment, public entrypoints, and runtime or platform evidence. If no such intent exists, report the missing intent instead of inventing a raw command.
|
|
64
64
|
|
|
65
65
|
<!-- mustflow-section: preconditions -->
|
|
66
66
|
## Preconditions
|
|
@@ -101,6 +101,14 @@ The release is not done when tests pass locally, a version string changes, or a
|
|
|
101
101
|
- GitHub Releases depend on Git tags, but release assets, checksums, signatures, and release body are separate evidence surfaces.
|
|
102
102
|
- App updater channels depend on metadata and signature state, not only uploaded installers.
|
|
103
103
|
5. For npm-style package publication, verify package metadata, packed file list, entrypoints, bin links, README, LICENSE, access, provenance or trusted publisher setup, registry target, and exact published version behavior through configured intents.
|
|
104
|
+
- Keep pre-publish packed-artifact checks separate from post-publish registry checks. A local tarball or workspace install does not prove that the immutable registry version users receive is installable.
|
|
105
|
+
- Run the post-publish smoke in a fresh consumer root outside the source checkout. Install the exact immutable name and version from the intended registry without workspace links or local archive fallbacks.
|
|
106
|
+
- Use a fresh or isolated package-manager cache strategy and prefer current registry metadata. A cache-only success is not independent remote-channel evidence.
|
|
107
|
+
- Disable lifecycle scripts by default so a package cannot repair missing build output during installation. If lifecycle scripts are an intentional public contract, test that behavior separately with the required sandbox and approval boundary.
|
|
108
|
+
- Execute the public command shims, exported entrypoints, or documented import path that users invoke. Calling an internal module file directly does not prove that package-manager shims, aliases, permissions, or entry metadata work.
|
|
109
|
+
- Exercise every documented command alias that is part of the release contract, then run one minimal documented workflow that crosses initialization or configuration validation when applicable.
|
|
110
|
+
- Retry only bounded transient registry conditions such as propagation delay, throttling, or transport reset. Do not retry authentication, package metadata, entrypoint, or runtime failures into a false pass.
|
|
111
|
+
- Keep temporary state owned and removable, preserve the primary failure if cleanup also fails, and report the exact package version, registry, package-manager version, runtime, operating system, and public entrypoints exercised.
|
|
104
112
|
6. For PyPI-style publication, verify source distribution, wheel contents, metadata, Python version constraints, entrypoints, README rendering, filename uniqueness, and install smoke path through configured intents.
|
|
105
113
|
7. For crates.io-style publication, verify manifest metadata, include and exclude rules, packaged file list, feature combinations, docs expectations, and yank-forward-fix policy.
|
|
106
114
|
8. For Go modules, treat the Git tag as the release. Verify module path, semantic tag, major-version path rules, tag target commit, proxy/cache implications, and module consumer smoke path. Do not move or delete tags as a casual recovery shortcut.
|
|
@@ -137,7 +145,7 @@ The release is not done when tests pass locally, a version string changes, or a
|
|
|
137
145
|
- Remote publication status is classified as not started, prepared, published, verified, failed, yanked, deprecated, superseded, or unknown.
|
|
138
146
|
- Branch, tag, and publication workflow checks are classified separately as green, failing, pending,
|
|
139
147
|
skipped, not applicable, or unknown.
|
|
140
|
-
-
|
|
148
|
+
- Independent consumer-root installation, pull, download, or updater smoke status through public entrypoints is known with environment evidence, or explicitly reported as skipped.
|
|
141
149
|
- Recovery plan matches the channel's actual permanence and rules.
|
|
142
150
|
|
|
143
151
|
<!-- mustflow-section: verification -->
|
|
@@ -155,7 +163,7 @@ Use configured oneshot command intents when available:
|
|
|
155
163
|
- `test_release`
|
|
156
164
|
- `mustflow_check`
|
|
157
165
|
|
|
158
|
-
Prefer configured release, package-inspection, artifact-inspection, install-smoke, updater-smoke, checksum, signature, provenance, or registry-verification intents when the command contract exposes them.
|
|
166
|
+
Prefer configured release, package-inspection, artifact-inspection, install-smoke, updater-smoke, checksum, signature, provenance, or registry-verification intents when the command contract exposes them. A local packed-artifact check may support pre-publish confidence, but it does not satisfy an independent post-publish installation claim.
|
|
159
167
|
|
|
160
168
|
Do not infer package manager, registry, Docker, Git, Homebrew, or updater commands from project files. If the needed intent is missing, report the missing command contract instead of writing a raw command into the skill or final release procedure.
|
|
161
169
|
|
|
@@ -180,7 +188,7 @@ Do not infer package manager, registry, Docker, Git, Homebrew, or updater comman
|
|
|
180
188
|
- Artifact contents inspected
|
|
181
189
|
- Remote publication state
|
|
182
190
|
- Branch, tag, and publication check-suite state
|
|
183
|
-
- User installation, download, pull, or updater smoke
|
|
191
|
+
- User installation, download, pull, or updater smoke result, including exact immutable version, registry or channel, public entrypoints, package manager, runtime, operating system, retry outcome, and skipped platforms
|
|
184
192
|
- Synchronized version, docs, manifest, workflow, and test surfaces
|
|
185
193
|
- Recovery or rollback classification
|
|
186
194
|
- Command intents run
|