mustflow 2.23.0 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/cli/commands/adapters.js +11 -9
- package/dist/cli/commands/api.js +263 -113
- package/dist/cli/commands/check.js +11 -7
- package/dist/cli/commands/classify.js +16 -42
- package/dist/cli/commands/context.js +18 -31
- package/dist/cli/commands/contract-lint.js +12 -7
- package/dist/cli/commands/dashboard.js +65 -114
- package/dist/cli/commands/docs.js +43 -26
- package/dist/cli/commands/doctor.js +11 -7
- package/dist/cli/commands/evidence.js +642 -0
- package/dist/cli/commands/explain-verify.js +1 -59
- package/dist/cli/commands/explain.js +84 -36
- package/dist/cli/commands/handoff.js +13 -17
- package/dist/cli/commands/impact.js +14 -20
- package/dist/cli/commands/index.js +15 -9
- package/dist/cli/commands/init.js +56 -70
- package/dist/cli/commands/line-endings.js +15 -9
- package/dist/cli/commands/map.js +30 -42
- package/dist/cli/commands/next.js +300 -0
- package/dist/cli/commands/onboard.js +136 -0
- package/dist/cli/commands/run.js +47 -42
- package/dist/cli/commands/search.js +43 -69
- package/dist/cli/commands/status.js +9 -6
- package/dist/cli/commands/update.js +16 -10
- package/dist/cli/commands/upgrade.js +9 -6
- package/dist/cli/commands/verify/args.js +55 -249
- package/dist/cli/commands/verify.js +2 -1
- package/dist/cli/commands/version-sources.js +9 -6
- package/dist/cli/commands/version.js +9 -6
- package/dist/cli/commands/workspace.js +564 -0
- package/dist/cli/i18n/en.js +60 -1
- package/dist/cli/i18n/es.js +60 -1
- package/dist/cli/i18n/fr.js +60 -1
- package/dist/cli/i18n/hi.js +60 -1
- package/dist/cli/i18n/ko.js +60 -1
- package/dist/cli/i18n/zh.js +60 -1
- package/dist/cli/index.js +28 -25
- package/dist/cli/lib/agent-context.js +8 -9
- package/dist/cli/lib/command-registry.js +24 -0
- package/dist/cli/lib/dashboard-html/client-script.js +1 -1
- package/dist/cli/lib/local-index/database-path.js +5 -0
- package/dist/cli/lib/local-index/database-read.js +88 -0
- package/dist/cli/lib/local-index/effect-graph-read-model.js +112 -0
- package/dist/cli/lib/local-index/freshness.js +60 -0
- package/dist/cli/lib/local-index/index.js +12 -1866
- package/dist/cli/lib/local-index/path-surface-read-model.js +134 -0
- package/dist/cli/lib/local-index/populate.js +474 -0
- package/dist/cli/lib/local-index/schema.js +413 -0
- package/dist/cli/lib/local-index/search-read-model.js +533 -0
- package/dist/cli/lib/local-index/search-text.js +79 -0
- package/dist/cli/lib/option-parser.js +93 -0
- package/dist/cli/lib/repo-map.js +2 -2
- package/dist/cli/lib/run-plan.js +5 -22
- package/dist/core/change-verification.js +11 -5
- package/dist/core/command-effects.js +1 -3
- package/dist/core/command-intent-eligibility.js +14 -0
- package/dist/core/command-preconditions.js +8 -4
- package/dist/core/command-run-constraints.js +43 -0
- package/dist/core/public-json-contracts.js +57 -0
- package/dist/core/test-selection.js +8 -2
- package/dist/core/verification-plan.js +32 -4
- package/package.json +1 -1
- package/schemas/README.md +16 -0
- package/schemas/api-serve-response.schema.json +89 -0
- package/schemas/change-verification-report.schema.json +4 -1
- package/schemas/contract-lint-report.schema.json +1 -0
- package/schemas/evidence-report.schema.json +287 -0
- package/schemas/explain-report.schema.json +4 -0
- package/schemas/next-report.schema.json +121 -0
- package/schemas/onboard-commands-report.schema.json +100 -0
- package/schemas/workspace-command-catalog.schema.json +172 -0
- package/schemas/workspace-status.schema.json +141 -0
- package/schemas/workspace-verification-plan.schema.json +195 -0
- package/templates/default/i18n.toml +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -1
- package/templates/default/locales/en/.mustflow/skills/clarifying-question-gate/SKILL.md +183 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +7 -1
- package/templates/default/locales/en/.mustflow/skills/structure-discovery-gate/SKILL.md +63 -20
- package/templates/default/manifest.toml +8 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { SEARCH_BACKEND_FTS5, SEARCH_BACKEND_TABLE_SCAN, TEST_DISABLE_FTS5_ENV, } from './constants.js';
|
|
2
|
+
export function toSearchString(value) {
|
|
3
|
+
if (value === null || value === undefined) {
|
|
4
|
+
return '';
|
|
5
|
+
}
|
|
6
|
+
if (value instanceof Uint8Array) {
|
|
7
|
+
return '';
|
|
8
|
+
}
|
|
9
|
+
return String(value);
|
|
10
|
+
}
|
|
11
|
+
export function queryRows(database, sql, params = []) {
|
|
12
|
+
const [result] = database.exec(sql, params);
|
|
13
|
+
if (!result) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
return result.values.map((values) => {
|
|
17
|
+
const row = {};
|
|
18
|
+
result.columns.forEach((column, index) => {
|
|
19
|
+
row[column] = values[index] ?? null;
|
|
20
|
+
});
|
|
21
|
+
return row;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function searchCapabilities(fts5Available) {
|
|
25
|
+
return {
|
|
26
|
+
backend: fts5Available ? SEARCH_BACKEND_FTS5 : SEARCH_BACKEND_TABLE_SCAN,
|
|
27
|
+
fts5Available,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function detectLocalSearchCapabilities(database) {
|
|
31
|
+
if (process.env[TEST_DISABLE_FTS5_ENV] === '1') {
|
|
32
|
+
return searchCapabilities(false);
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
database.run('CREATE VIRTUAL TABLE temp.mustflow_fts5_probe USING fts5(value)');
|
|
36
|
+
database.run('DROP TABLE temp.mustflow_fts5_probe');
|
|
37
|
+
return searchCapabilities(true);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return searchCapabilities(false);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function readMetadataValue(database, key) {
|
|
44
|
+
return toSearchString(queryRows(database, 'SELECT value FROM metadata WHERE key = ?', [key])[0]?.value) || undefined;
|
|
45
|
+
}
|
|
46
|
+
export function hasTable(database, tableName) {
|
|
47
|
+
return queryRows(database, 'SELECT name FROM sqlite_master WHERE type = "table" AND name = ?', [tableName]).length > 0;
|
|
48
|
+
}
|
|
49
|
+
export function readStoredSearchCapabilities(database) {
|
|
50
|
+
const fts5Available = readMetadataValue(database, 'search_fts5_available') === 'true';
|
|
51
|
+
const backend = readMetadataValue(database, 'search_backend');
|
|
52
|
+
if (backend === SEARCH_BACKEND_FTS5 && hasTable(database, 'search_documents_fts')) {
|
|
53
|
+
return { backend: SEARCH_BACKEND_FTS5, fts5Available };
|
|
54
|
+
}
|
|
55
|
+
return { backend: SEARCH_BACKEND_TABLE_SCAN, fts5Available };
|
|
56
|
+
}
|
|
57
|
+
export function toNullableNumber(value) {
|
|
58
|
+
if (typeof value !== 'number') {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return Number.isFinite(value) ? value : null;
|
|
62
|
+
}
|
|
63
|
+
export function splitIndexedList(value) {
|
|
64
|
+
return toSearchString(value)
|
|
65
|
+
.split(',')
|
|
66
|
+
.map((item) => item.trim())
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
.sort((left, right) => left.localeCompare(right));
|
|
69
|
+
}
|
|
70
|
+
export function readCount(database, tableName) {
|
|
71
|
+
if (!hasTable(database, tableName)) {
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
const [row] = queryRows(database, `SELECT COUNT(*) AS count FROM ${tableName}`);
|
|
75
|
+
const count = row?.count;
|
|
76
|
+
return typeof count === 'number' && Number.isFinite(count) ? count : 0;
|
|
77
|
+
}
|
|
78
|
+
export function readStoredIndexedPaths(database) {
|
|
79
|
+
if (!hasTable(database, 'documents')) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
return queryRows(database, 'SELECT path FROM documents ORDER BY path')
|
|
83
|
+
.map((row) => toSearchString(row.path))
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
export function sqlPlaceholders(values) {
|
|
87
|
+
return values.map(() => '?').join(', ');
|
|
88
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { getLocalIndexDatabasePath } from './database-path.js';
|
|
3
|
+
import { queryRows, splitIndexedList, toSearchString } from './database-read.js';
|
|
4
|
+
import { getStalePaths } from './freshness.js';
|
|
5
|
+
import { loadSqlJs } from './sql.js';
|
|
6
|
+
function createCommandEffectGraphStatus(databasePath, status, stalePaths = []) {
|
|
7
|
+
return {
|
|
8
|
+
source: 'local_index',
|
|
9
|
+
authority: 'explanation_only',
|
|
10
|
+
commandAuthority: '.mustflow/config/commands.toml',
|
|
11
|
+
grantsCommandAuthority: false,
|
|
12
|
+
status,
|
|
13
|
+
databasePath,
|
|
14
|
+
indexFresh: status === 'fresh',
|
|
15
|
+
stalePaths,
|
|
16
|
+
writeLocks: [],
|
|
17
|
+
lockConflicts: [],
|
|
18
|
+
refreshHint: status === 'fresh' ? null : 'Run `mf index` to refresh command-effect graph explanations.',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function mapCommandLockConflict(row, intent) {
|
|
22
|
+
const targetIsLeft = toSearchString(row.left_intent) === intent;
|
|
23
|
+
const targetPrefix = targetIsLeft ? 'left' : 'right';
|
|
24
|
+
const otherPrefix = targetIsLeft ? 'right' : 'left';
|
|
25
|
+
return {
|
|
26
|
+
intent: toSearchString(row[`${otherPrefix}_intent`]),
|
|
27
|
+
lock: toSearchString(row.lock),
|
|
28
|
+
paths: splitIndexedList(row[`${targetPrefix}_paths`]),
|
|
29
|
+
modes: splitIndexedList(row[`${targetPrefix}_modes`]),
|
|
30
|
+
concurrencies: splitIndexedList(row[`${targetPrefix}_concurrencies`]),
|
|
31
|
+
conflictingPaths: splitIndexedList(row[`${otherPrefix}_paths`]),
|
|
32
|
+
conflictingModes: splitIndexedList(row[`${otherPrefix}_modes`]),
|
|
33
|
+
conflictingConcurrencies: splitIndexedList(row[`${otherPrefix}_concurrencies`]),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* mf:anchor cli.index.command-effect-graph
|
|
38
|
+
* purpose: Read command-effect lock and conflict explanations from the local SQLite index.
|
|
39
|
+
* search: mf explain command, command locks, local index, sqlite graph
|
|
40
|
+
* invariant: Indexed command-effect rows explain current commands.toml only when the index is fresh and never grant command authority.
|
|
41
|
+
* risk: cache, config
|
|
42
|
+
*/
|
|
43
|
+
function queryLocalCommandEffectGraph(databasePath, database, intent) {
|
|
44
|
+
const writeLocks = queryRows(database, `
|
|
45
|
+
SELECT lock, paths, modes, sources, concurrencies, effect_count
|
|
46
|
+
FROM command_write_locks
|
|
47
|
+
WHERE intent = ?
|
|
48
|
+
ORDER BY lock
|
|
49
|
+
`, [intent]).map((row) => ({
|
|
50
|
+
lock: toSearchString(row.lock),
|
|
51
|
+
paths: splitIndexedList(row.paths),
|
|
52
|
+
modes: splitIndexedList(row.modes),
|
|
53
|
+
sources: splitIndexedList(row.sources),
|
|
54
|
+
concurrencies: splitIndexedList(row.concurrencies),
|
|
55
|
+
effectCount: typeof row.effect_count === 'number' && Number.isFinite(row.effect_count) ? row.effect_count : 0,
|
|
56
|
+
}));
|
|
57
|
+
const lockConflicts = queryRows(database, `
|
|
58
|
+
SELECT
|
|
59
|
+
left_intent,
|
|
60
|
+
right_intent,
|
|
61
|
+
lock,
|
|
62
|
+
left_paths,
|
|
63
|
+
right_paths,
|
|
64
|
+
left_modes,
|
|
65
|
+
right_modes,
|
|
66
|
+
left_concurrencies,
|
|
67
|
+
right_concurrencies
|
|
68
|
+
FROM command_lock_conflicts
|
|
69
|
+
WHERE left_intent = ? OR right_intent = ?
|
|
70
|
+
ORDER BY lock, left_intent, right_intent
|
|
71
|
+
`, [intent, intent]).map((row) => mapCommandLockConflict(row, intent));
|
|
72
|
+
return {
|
|
73
|
+
source: 'local_index',
|
|
74
|
+
authority: 'explanation_only',
|
|
75
|
+
commandAuthority: '.mustflow/config/commands.toml',
|
|
76
|
+
grantsCommandAuthority: false,
|
|
77
|
+
status: 'fresh',
|
|
78
|
+
databasePath,
|
|
79
|
+
indexFresh: true,
|
|
80
|
+
stalePaths: [],
|
|
81
|
+
writeLocks,
|
|
82
|
+
lockConflicts,
|
|
83
|
+
refreshHint: null,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export async function readLocalCommandEffectGraph(projectRoot, intent) {
|
|
87
|
+
const graphs = await readLocalCommandEffectGraphs(projectRoot, [intent]);
|
|
88
|
+
return graphs.get(intent) ?? createCommandEffectGraphStatus(getLocalIndexDatabasePath(projectRoot), 'unreadable');
|
|
89
|
+
}
|
|
90
|
+
export async function readLocalCommandEffectGraphs(projectRoot, intents) {
|
|
91
|
+
const databasePath = getLocalIndexDatabasePath(projectRoot);
|
|
92
|
+
const intentNames = [...new Set(intents)];
|
|
93
|
+
const statusMap = (status, stalePaths = []) => new Map(intentNames.map((intent) => [intent, createCommandEffectGraphStatus(databasePath, status, stalePaths)]));
|
|
94
|
+
if (!existsSync(databasePath)) {
|
|
95
|
+
return statusMap('missing');
|
|
96
|
+
}
|
|
97
|
+
const SQL = await loadSqlJs();
|
|
98
|
+
const database = new SQL.Database(readFileSync(databasePath));
|
|
99
|
+
try {
|
|
100
|
+
const stalePaths = getStalePaths(projectRoot, database);
|
|
101
|
+
if (stalePaths.length > 0) {
|
|
102
|
+
return statusMap('stale', stalePaths);
|
|
103
|
+
}
|
|
104
|
+
return new Map(intentNames.map((intent) => [intent, queryLocalCommandEffectGraph(databasePath, database, intent)]));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return statusMap('unreadable');
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
database.close();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { LOCAL_INDEX_SCHEMA_VERSION } from './constants.js';
|
|
2
|
+
import { hasTable, queryRows, readMetadataValue, toSearchString } from './database-read.js';
|
|
3
|
+
import { normalizeIndexedFileSourceScope, readIndexedFileRecord } from './source-index.js';
|
|
4
|
+
import { collectDocuments } from './workflow-documents.js';
|
|
5
|
+
export function readStoredSchemaVersion(database) {
|
|
6
|
+
return readMetadataValue(database, 'schema_version');
|
|
7
|
+
}
|
|
8
|
+
export function getStalePaths(projectRoot, database) {
|
|
9
|
+
const schemaVersion = readStoredSchemaVersion(database);
|
|
10
|
+
if (schemaVersion !== LOCAL_INDEX_SCHEMA_VERSION) {
|
|
11
|
+
return ['.mustflow/cache/mustflow.sqlite'];
|
|
12
|
+
}
|
|
13
|
+
if (hasTable(database, 'indexed_files')) {
|
|
14
|
+
const stalePaths = new Set();
|
|
15
|
+
const indexedRows = queryRows(database, 'SELECT path, source_scope, content_hash FROM indexed_files');
|
|
16
|
+
const indexedPaths = new Set(indexedRows.map((row) => toSearchString(row.path)));
|
|
17
|
+
for (const row of indexedRows) {
|
|
18
|
+
const indexedPath = toSearchString(row.path);
|
|
19
|
+
const sourceScope = normalizeIndexedFileSourceScope(toSearchString(row.source_scope));
|
|
20
|
+
try {
|
|
21
|
+
const current = readIndexedFileRecord(projectRoot, indexedPath, sourceScope);
|
|
22
|
+
if (current.contentHash !== toSearchString(row.content_hash)) {
|
|
23
|
+
stalePaths.add(indexedPath);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
stalePaths.add(indexedPath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const document of collectDocuments(projectRoot)) {
|
|
31
|
+
if (!indexedPaths.has(document.path)) {
|
|
32
|
+
stalePaths.add(document.path);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return Array.from(stalePaths).sort((left, right) => left.localeCompare(right));
|
|
36
|
+
}
|
|
37
|
+
const indexedRows = queryRows(database, 'SELECT path, content_hash FROM documents');
|
|
38
|
+
const indexedHashes = new Map(indexedRows.map((row) => [toSearchString(row.path), toSearchString(row.content_hash)]));
|
|
39
|
+
const currentDocuments = collectDocuments(projectRoot);
|
|
40
|
+
const currentHashes = new Map(currentDocuments.map((document) => [document.path, document.contentHash]));
|
|
41
|
+
const stalePaths = new Set();
|
|
42
|
+
for (const [indexedPath, indexedHash] of indexedHashes) {
|
|
43
|
+
if (currentHashes.get(indexedPath) !== indexedHash) {
|
|
44
|
+
stalePaths.add(indexedPath);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const currentPath of currentHashes.keys()) {
|
|
48
|
+
if (!indexedHashes.has(currentPath)) {
|
|
49
|
+
stalePaths.add(currentPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Array.from(stalePaths).sort((left, right) => left.localeCompare(right));
|
|
53
|
+
}
|
|
54
|
+
export function isLocalIndexStaleError(error) {
|
|
55
|
+
return error instanceof Error && error.message.startsWith('Local mustflow index is stale:');
|
|
56
|
+
}
|
|
57
|
+
export function isLocalIndexRuntimeUnavailableError(error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
+
return /file is not a database|database disk image is malformed|no such table|no such column|sqlite|sql\.js/iu.test(message);
|
|
60
|
+
}
|