@moda-ai/cli 1.50.0 → 1.54.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 +14 -2
- package/dist/{cli-x0x0n35w.js → cli-9250ckvg.js} +19 -15
- package/dist/{cli-jqfm8dp4.js → cli-a2jq9yd5.js} +386 -83
- package/dist/{cli-xrs02c0t.js → cli-ac3p2byq.js} +3 -1
- package/dist/{cli-7kbzqact.js → cli-kxwm7y62.js} +1 -1
- package/dist/cli-mm48br00.js +47 -0
- package/dist/{cli-h4k23fb0.js → cli-sq6g0tk5.js} +2 -2
- package/dist/cli.js +175 -41
- package/dist/{harness-github-actions-q9kewzb6.js → harness-github-actions-075xzdvp.js} +2 -2
- package/dist/{harness-7gr0xbtc.js → harness-sd44jsaw.js} +2 -2
- package/dist/{index-z0jpjcsm.js → index-hn9t95tr.js} +6 -5
- package/dist/prompt-hash.d.ts +36 -0
- package/dist/prompt-hash.js +15 -0
- package/dist/prompt-source-mcp.js +2 -2
- package/dist/{provision-qkazpt0c.js → provision-d1dfj6as.js} +3 -3
- package/package.json +10 -2
- package/skills/integration/index.json +3 -3
- package/skills/integration/integration-cloudflare-think/SKILL.md +5 -2
- package/skills/moda-cli/SKILL.md +67 -15
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
computePromptContentHash,
|
|
3
|
+
stableJson
|
|
4
|
+
} from "./cli-mm48br00.js";
|
|
5
|
+
import {
|
|
6
|
+
$isSeq,
|
|
2
7
|
$parseDocument,
|
|
3
8
|
$stringify,
|
|
4
9
|
assertFlags,
|
|
5
10
|
assertPositionals,
|
|
11
|
+
backfillPromptSource,
|
|
6
12
|
callControlAPI,
|
|
7
13
|
callDataAPI,
|
|
8
14
|
inventoryFiles,
|
|
15
|
+
normalizeHarnessReport,
|
|
9
16
|
readHarnessSyncState,
|
|
10
17
|
renderDoctorHuman,
|
|
11
18
|
renderStatusHuman,
|
|
@@ -13,7 +20,7 @@ import {
|
|
|
13
20
|
resolvePromptSource,
|
|
14
21
|
safePromptEvidencePath,
|
|
15
22
|
scanHarness
|
|
16
|
-
} from "./cli-
|
|
23
|
+
} from "./cli-9250ckvg.js";
|
|
17
24
|
import {
|
|
18
25
|
SKILL_SOURCE_HARNESS,
|
|
19
26
|
SKILL_SOURCE_INTEGRATION_BUNDLED,
|
|
@@ -26,7 +33,7 @@ import {
|
|
|
26
33
|
import {
|
|
27
34
|
isAuthSessionValid,
|
|
28
35
|
loadAuthSession
|
|
29
|
-
} from "./cli-
|
|
36
|
+
} from "./cli-kxwm7y62.js";
|
|
30
37
|
import {
|
|
31
38
|
CliInputError,
|
|
32
39
|
EXIT_ERROR,
|
|
@@ -45,7 +52,7 @@ import {
|
|
|
45
52
|
resolveTenantId,
|
|
46
53
|
secretFilePathFromRef,
|
|
47
54
|
validateConfig
|
|
48
|
-
} from "./cli-
|
|
55
|
+
} from "./cli-ac3p2byq.js";
|
|
49
56
|
import {
|
|
50
57
|
__commonJS,
|
|
51
58
|
__require,
|
|
@@ -6463,6 +6470,7 @@ function checkPromptAttribution(rootDir, keys) {
|
|
|
6463
6470
|
var MODA_DIR = ".moda";
|
|
6464
6471
|
var MANIFEST_PATH = join(MODA_DIR, "prompts.yml");
|
|
6465
6472
|
var LOCK_PATH = join(MODA_DIR, "prompts.lock.json");
|
|
6473
|
+
var DISCOVERY_IGNORE = ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**", "**/.env*", "**/*.pem", "**/*.key"];
|
|
6466
6474
|
var DEFAULT_PROMPT_PATHS = [
|
|
6467
6475
|
"prompts/**/*.prompt.md",
|
|
6468
6476
|
"prompts/**/*.prompt.json",
|
|
@@ -6473,6 +6481,7 @@ var PROMPT_WRITE_SUBCOMMAND_FLAGS = {
|
|
|
6473
6481
|
init: ["from-harness"],
|
|
6474
6482
|
status: [],
|
|
6475
6483
|
diff: [],
|
|
6484
|
+
migrate: ["dry-run", "discover"],
|
|
6476
6485
|
sync: [
|
|
6477
6486
|
"dry-run",
|
|
6478
6487
|
"from-harness",
|
|
@@ -6577,18 +6586,41 @@ async function runPromptsCommand(input, legacyFlags, legacyProfileOptions) {
|
|
|
6577
6586
|
kind: "prompt_manifest",
|
|
6578
6587
|
path: init.manifestPath,
|
|
6579
6588
|
description: init.created ? "Prompt discovery manifest created by this run." : "Prompt discovery manifest already existed; left unchanged."
|
|
6580
|
-
}]
|
|
6589
|
+
}],
|
|
6590
|
+
...imported?.skipped.length ? { warnings: [harnessSkipWarning(imported)] } : {}
|
|
6581
6591
|
});
|
|
6582
6592
|
break;
|
|
6583
6593
|
}
|
|
6584
|
-
case "status":
|
|
6585
|
-
|
|
6594
|
+
case "status": {
|
|
6595
|
+
const status = await getPromptStatus();
|
|
6596
|
+
const warnings = promptManifestWarnings(status);
|
|
6597
|
+
context.output.writeData(status, warnings.length > 0 ? { warnings } : undefined);
|
|
6586
6598
|
break;
|
|
6599
|
+
}
|
|
6600
|
+
case "migrate": {
|
|
6601
|
+
assertPositionals("prompts migrate", positionals, 1, "moda prompts migrate [--dry-run] [--discover]");
|
|
6602
|
+
const result = await migratePromptManifest({
|
|
6603
|
+
dryRun: flags["dry-run"] === "true",
|
|
6604
|
+
discover: flags.discover === "true",
|
|
6605
|
+
env: profileOptions.env
|
|
6606
|
+
});
|
|
6607
|
+
context.output.writeData(result, {
|
|
6608
|
+
summary: {
|
|
6609
|
+
text: !result.changed ? `${MANIFEST_PATH} is already version 2 with nothing to add.` : `${result.dryRun ? "Would migrate" : "Migrated"} ${MANIFEST_PATH} from version ${result.fromVersion} to 2: ${result.addedPaths.length} legacy-discovered path(s), ${result.addedSources.length} discovered source(s) added.`,
|
|
6610
|
+
confidence: "high"
|
|
6611
|
+
},
|
|
6612
|
+
...result.changed && !result.dryRun ? {
|
|
6613
|
+
artifacts: [{ kind: "prompt_manifest", path: result.manifestPath, description: "Prompt manifest rewritten to version 2." }]
|
|
6614
|
+
} : {},
|
|
6615
|
+
...migrateWarnings(result).length > 0 ? { warnings: migrateWarnings(result) } : {}
|
|
6616
|
+
});
|
|
6617
|
+
break;
|
|
6618
|
+
}
|
|
6587
6619
|
case "diff": {
|
|
6588
6620
|
const status = await getPromptStatus();
|
|
6589
6621
|
const differing = status.prompts.filter((row) => row.state !== "unchanged");
|
|
6590
6622
|
const tracked = existsSync2(MANIFEST_PATH);
|
|
6591
|
-
const
|
|
6623
|
+
const diffWarnings = tracked ? promptManifestWarnings(status) : [
|
|
6592
6624
|
`No ${MANIFEST_PATH} here, so diff has no last sync to compare against: it only covers prompts tracked by \`moda prompts sync\`${differing.length > 0 ? ", and every discovered prompt file lists as new" : ""}. For a registry manifest, preview with \`moda registry push --dry-run\`.`
|
|
6593
6625
|
];
|
|
6594
6626
|
context.output.writeData({
|
|
@@ -6601,7 +6633,7 @@ async function runPromptsCommand(input, legacyFlags, legacyProfileOptions) {
|
|
|
6601
6633
|
text: !tracked ? differing.length === 0 ? `Not a prompts-sync project (no ${MANIFEST_PATH}) and no prompt files found; diff has nothing to compare.` : `Not a prompts-sync project (no ${MANIFEST_PATH}): ${differing.length} discovered prompt file(s) are untracked and would be new on \`moda prompts sync\`.` : differing.length === 0 ? "No prompt differs from the last sync." : `${differing.length} prompt(s) differ from the last sync (changed ${status.changed}, new ${status.new}, deleted ${status.deleted}).`,
|
|
6602
6634
|
confidence: "high"
|
|
6603
6635
|
},
|
|
6604
|
-
...
|
|
6636
|
+
...diffWarnings.length > 0 ? { warnings: diffWarnings } : {}
|
|
6605
6637
|
});
|
|
6606
6638
|
break;
|
|
6607
6639
|
}
|
|
@@ -6625,7 +6657,7 @@ async function runPromptsCommand(input, legacyFlags, legacyProfileOptions) {
|
|
|
6625
6657
|
await runPromptRegistryRead(subcommand, positionals, flags, profileOptions, context);
|
|
6626
6658
|
break;
|
|
6627
6659
|
default:
|
|
6628
|
-
throw new Error(`Unknown prompts command '${subcommand}'. Use init, status, diff, sync, promote, ab, propose, list, show, proposals, usage, replay-runs, or decide.`);
|
|
6660
|
+
throw new Error(`Unknown prompts command '${subcommand}'. Use init, status, diff, migrate, sync, promote, ab, propose, list, show, proposals, usage, replay-runs, or decide.`);
|
|
6629
6661
|
}
|
|
6630
6662
|
return 0;
|
|
6631
6663
|
}
|
|
@@ -6640,7 +6672,7 @@ async function initPrompts(options = {}) {
|
|
|
6640
6672
|
if (!existsSync2(MANIFEST_PATH)) {
|
|
6641
6673
|
assertContainedSource(cwd, MODA_DIR);
|
|
6642
6674
|
try {
|
|
6643
|
-
writeFileSync2(manifestPath, ["version:
|
|
6675
|
+
writeFileSync2(manifestPath, ["version: 2", "prompt_paths:", ...DEFAULT_PROMPT_PATHS.map((path) => ` - "${path}"`), "sources: []", ""].join(`
|
|
6644
6676
|
`), { flag: "wx" });
|
|
6645
6677
|
created = true;
|
|
6646
6678
|
} catch (error) {
|
|
@@ -6698,20 +6730,26 @@ async function runPromptSync(flags, profileOptions, options = {}) {
|
|
|
6698
6730
|
}
|
|
6699
6731
|
}
|
|
6700
6732
|
async function runPromptSyncExclusive(flags, profileOptions, options) {
|
|
6733
|
+
let harnessImport;
|
|
6701
6734
|
if (flags["from-harness"] && flags["dry-run"] !== "true")
|
|
6702
|
-
importHarnessPromptSources(flags["from-harness"]);
|
|
6735
|
+
harnessImport = importHarnessPromptSources(flags["from-harness"]);
|
|
6703
6736
|
if (flags["dry-run"] !== "true")
|
|
6704
6737
|
await initPrompts({ silent: true });
|
|
6705
6738
|
const manifest = readManifest();
|
|
6706
6739
|
const originalManifestHash = hashFile(MANIFEST_PATH);
|
|
6707
|
-
let extraSources =
|
|
6740
|
+
let extraSources = [];
|
|
6741
|
+
if (flags["from-harness"] && flags["dry-run"] === "true") {
|
|
6742
|
+
const read = readHarnessSources(flags["from-harness"]);
|
|
6743
|
+
extraSources = read.sources;
|
|
6744
|
+
harnessImport = read.result;
|
|
6745
|
+
}
|
|
6708
6746
|
mergePromptSources(manifest.sources, extraSources);
|
|
6709
6747
|
const established = discoverPrompts(extraSources);
|
|
6710
6748
|
let discovery;
|
|
6711
6749
|
const analyzedDefinitions = new Map;
|
|
6712
6750
|
if (flags["no-analyze"] !== "true") {
|
|
6713
6751
|
options.onProgress?.("Analyzing prompt sources before sync.");
|
|
6714
|
-
const { analyzeHarnessPrompts } = await import("./harness-
|
|
6752
|
+
const { analyzeHarnessPrompts } = await import("./harness-sd44jsaw.js");
|
|
6715
6753
|
const result = await analyzeHarnessPrompts({
|
|
6716
6754
|
rootDir: process.cwd(),
|
|
6717
6755
|
existing: established.flatMap((prompt) => prompt.sourceDefinition ? [prompt.sourceDefinition.source] : []),
|
|
@@ -6761,7 +6799,7 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
|
|
|
6761
6799
|
if (prompts.length === 0) {
|
|
6762
6800
|
if (!dryRun && flags["allow-untrack"] === "true")
|
|
6763
6801
|
writeLock([], []);
|
|
6764
|
-
return { synced: [], dryRun, discovery, attribution };
|
|
6802
|
+
return { synced: [], dryRun, discovery, attribution, ...harnessImport ? { harnessImport } : {} };
|
|
6765
6803
|
}
|
|
6766
6804
|
const signature = stableJson(prompts);
|
|
6767
6805
|
const response = await callControlAPI("/prompts/sync", {
|
|
@@ -6793,7 +6831,7 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
|
|
|
6793
6831
|
atomicWrite(MANIFEST_PATH, manifestUpdate);
|
|
6794
6832
|
writeLock(prompts, synced);
|
|
6795
6833
|
}
|
|
6796
|
-
return { synced: response.synced ?? [], dryRun, discovery, attribution };
|
|
6834
|
+
return { synced: response.synced ?? [], dryRun, discovery, attribution, ...harnessImport ? { harnessImport } : {} };
|
|
6797
6835
|
}
|
|
6798
6836
|
function writePromptSyncProgress(message, context, quiet = false) {
|
|
6799
6837
|
if (quiet || context?.quiet)
|
|
@@ -6813,10 +6851,12 @@ async function syncPromptsOnce(flags, profileOptions, context) {
|
|
|
6813
6851
|
synced: result.synced,
|
|
6814
6852
|
discovery: result.discovery,
|
|
6815
6853
|
attribution: result.attribution,
|
|
6854
|
+
...result.harnessImport ? { harnessImport: result.harnessImport } : {},
|
|
6816
6855
|
lockfile: result.dryRun ? undefined : LOCK_PATH
|
|
6817
6856
|
};
|
|
6857
|
+
const skippedWarning = result.harnessImport?.skipped.length ? [harnessSkipWarning(result.harnessImport)] : [];
|
|
6818
6858
|
if (context)
|
|
6819
|
-
context.output.writeData(payload);
|
|
6859
|
+
context.output.writeData(payload, skippedWarning.length ? { warnings: skippedWarning } : undefined);
|
|
6820
6860
|
else
|
|
6821
6861
|
printJson(payload);
|
|
6822
6862
|
if (!result.dryRun && result.synced.length === 0 && result.discovery?.candidates?.length) {
|
|
@@ -6885,9 +6925,15 @@ async function getPromptStatus() {
|
|
|
6885
6925
|
});
|
|
6886
6926
|
}
|
|
6887
6927
|
}
|
|
6928
|
+
const inspection = inspectPromptManifest();
|
|
6888
6929
|
return {
|
|
6889
6930
|
manifest: MANIFEST_PATH,
|
|
6890
6931
|
lockfile: LOCK_PATH,
|
|
6932
|
+
manifestVersion: inspection.manifestVersion,
|
|
6933
|
+
legacyManifest: inspection.legacyManifest,
|
|
6934
|
+
zeroMatchPatterns: inspection.zeroMatchPatterns,
|
|
6935
|
+
legacyOnlyFiles: inspection.legacyOnlyFiles,
|
|
6936
|
+
sources: inspection.sources,
|
|
6891
6937
|
total: rows.length,
|
|
6892
6938
|
changed: rows.filter((row) => row.state === "changed").length,
|
|
6893
6939
|
new: rows.filter((row) => row.state === "new").length,
|
|
@@ -6896,7 +6942,9 @@ async function getPromptStatus() {
|
|
|
6896
6942
|
};
|
|
6897
6943
|
}
|
|
6898
6944
|
function discoverPrompts(extraSources = []) {
|
|
6899
|
-
|
|
6945
|
+
return discoverManifestPrompts(readManifest(), extraSources);
|
|
6946
|
+
}
|
|
6947
|
+
function discoverManifestPrompts(manifest, extraSources = []) {
|
|
6900
6948
|
const root = process.cwd();
|
|
6901
6949
|
for (const pattern of manifest.promptPaths) {
|
|
6902
6950
|
const positive = pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
@@ -6912,7 +6960,7 @@ function discoverPrompts(extraSources = []) {
|
|
|
6912
6960
|
followSymbolicLinks: false,
|
|
6913
6961
|
dot: true,
|
|
6914
6962
|
unique: true,
|
|
6915
|
-
ignore:
|
|
6963
|
+
ignore: DISCOVERY_IGNORE
|
|
6916
6964
|
}).sort();
|
|
6917
6965
|
const sources = new Map(manifest.sources.map((source) => [source.key, source]));
|
|
6918
6966
|
for (const source of extraSources)
|
|
@@ -6934,6 +6982,185 @@ function discoverPrompts(extraSources = []) {
|
|
|
6934
6982
|
}
|
|
6935
6983
|
return prompts.sort((a, b) => a.key.localeCompare(b.key));
|
|
6936
6984
|
}
|
|
6985
|
+
var LEGACY_PROMPT_SUFFIXES = [".prompt.md", ".prompt.json", ".prompt.yaml", ".prompt.yml"];
|
|
6986
|
+
function repoPath(root, path) {
|
|
6987
|
+
return relative(root, resolve4(root, path)).replaceAll("\\", "/");
|
|
6988
|
+
}
|
|
6989
|
+
function globPromptFiles(root, patterns, ignore = []) {
|
|
6990
|
+
if (patterns.length === 0)
|
|
6991
|
+
return [];
|
|
6992
|
+
return import_fast_glob.default.sync(patterns, {
|
|
6993
|
+
cwd: root,
|
|
6994
|
+
onlyFiles: true,
|
|
6995
|
+
followSymbolicLinks: false,
|
|
6996
|
+
dot: true,
|
|
6997
|
+
unique: true,
|
|
6998
|
+
ignore: [...DISCOVERY_IGNORE, ...ignore]
|
|
6999
|
+
}).sort();
|
|
7000
|
+
}
|
|
7001
|
+
function inspectPromptManifest(root = process.cwd()) {
|
|
7002
|
+
const manifest = readManifest(root);
|
|
7003
|
+
const safe = (pattern) => !isAbsolute3(pattern) && !pattern.split(/[\\/]/).includes("..");
|
|
7004
|
+
const positives = manifest.promptPaths.filter((pattern) => !pattern.startsWith("!") && safe(pattern));
|
|
7005
|
+
const negations = manifest.promptPaths.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
|
|
7006
|
+
const patterns = manifest.promptPaths.filter((pattern) => !pattern.startsWith("!")).map((pattern) => ({
|
|
7007
|
+
pattern,
|
|
7008
|
+
matches: safe(pattern) ? globPromptFiles(root, [pattern], negations).length : 0
|
|
7009
|
+
}));
|
|
7010
|
+
const matched = new Set(globPromptFiles(root, positives, negations));
|
|
7011
|
+
const owned = new Set(manifest.sources.map((source) => repoPath(root, source.path)));
|
|
7012
|
+
const suffixes = [...new Set(manifest.promptPaths.filter((pattern) => pattern.includes(".prompt.")).flatMap((pattern) => LEGACY_PROMPT_SUFFIXES.filter((suffix) => pattern.endsWith(suffix))))];
|
|
7013
|
+
const legacySuffixes = suffixes.length ? suffixes : [".prompt.md", ".prompt.json"];
|
|
7014
|
+
const tracked = manifest.version === 2 ? lockedSourcePaths(root) : null;
|
|
7015
|
+
const legacyOnlyFiles = manifest.version === null ? [] : globPromptFiles(root, legacySuffixes.map((suffix) => `**/*${suffix}`), negations).filter((path) => !matched.has(path) && !owned.has(path) && (!tracked || tracked.has(path)));
|
|
7016
|
+
return {
|
|
7017
|
+
manifestExists: manifest.version !== null,
|
|
7018
|
+
manifestVersion: manifest.version,
|
|
7019
|
+
legacyManifest: manifest.version === 1,
|
|
7020
|
+
patterns,
|
|
7021
|
+
zeroMatchPatterns: patterns.filter((row) => row.matches === 0).map((row) => row.pattern),
|
|
7022
|
+
sources: manifest.sources.length,
|
|
7023
|
+
matchedFiles: matched.size,
|
|
7024
|
+
legacyOnlyFiles
|
|
7025
|
+
};
|
|
7026
|
+
}
|
|
7027
|
+
function lockedSourcePaths(root) {
|
|
7028
|
+
const lockPath = resolve4(root, LOCK_PATH);
|
|
7029
|
+
if (!existsSync2(lockPath))
|
|
7030
|
+
return new Set;
|
|
7031
|
+
try {
|
|
7032
|
+
const lock = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
7033
|
+
return new Set(Object.values(lock.prompts ?? {}).filter((entry) => entry.sourcePath).map((entry) => repoPath(root, String(entry.sourcePath))));
|
|
7034
|
+
} catch {
|
|
7035
|
+
return new Set;
|
|
7036
|
+
}
|
|
7037
|
+
}
|
|
7038
|
+
function promptManifestWarnings(inspection) {
|
|
7039
|
+
if (inspection.manifestVersion === null)
|
|
7040
|
+
return [];
|
|
7041
|
+
const warnings = [];
|
|
7042
|
+
const legacyCount = inspection.legacyOnlyFiles.length;
|
|
7043
|
+
if (legacyCount > 0) {
|
|
7044
|
+
const sample = inspection.legacyOnlyFiles.slice(0, 3).join(", ");
|
|
7045
|
+
warnings.push(`${legacyCount} prompt file(s) (${sample}${legacyCount > 3 ? ", ..." : ""}) were discovered by older CLIs but match no ${MANIFEST_PATH} pattern now: patterns are exact globs from the repository root. Run \`moda prompts migrate --dry-run\`, then \`moda prompts migrate\`, to keep them tracked under their existing keys.`);
|
|
7046
|
+
}
|
|
7047
|
+
if (inspection.zeroMatchPatterns.length > 0) {
|
|
7048
|
+
warnings.push(`prompt_paths pattern(s) match no files: ${inspection.zeroMatchPatterns.join(", ")}. Fix the globs (relative to the repository root), map code-defined prompts under \`sources:\`, or preview discovery with \`moda prompts sync --dry-run\`.`);
|
|
7049
|
+
}
|
|
7050
|
+
if (inspection.legacyManifest && warnings.length > 0) {
|
|
7051
|
+
warnings.push(`${MANIFEST_PATH} is a version 1 manifest; \`moda prompts migrate\` rewrites it to version 2 (explicit prompt_paths plus a sources: list).`);
|
|
7052
|
+
}
|
|
7053
|
+
return warnings;
|
|
7054
|
+
}
|
|
7055
|
+
function migrateWarnings(result) {
|
|
7056
|
+
const warnings = [];
|
|
7057
|
+
if (result.zeroMatchPatterns.length > 0) {
|
|
7058
|
+
warnings.push(`Kept prompt_paths pattern(s) that match no files: ${result.zeroMatchPatterns.join(", ")}. Fix or remove them.`);
|
|
7059
|
+
}
|
|
7060
|
+
if (result.pendingSources.length > 0) {
|
|
7061
|
+
const sample = result.pendingSources.slice(0, 3).map((source) => source.key).join(", ");
|
|
7062
|
+
warnings.push(`${result.pendingSources.length} code-defined prompt candidate(s) (${sample}${result.pendingSources.length > 3 ? ", ..." : ""}) were found but not added: --discover never runs an analyst, and code mappings need independent verification. Run \`${HARNESS_REANALYZE_COMMAND}\`, then \`moda prompts sync --from-harness\`, or map them under \`sources:\` yourself. See pendingSources.`);
|
|
7063
|
+
}
|
|
7064
|
+
return warnings;
|
|
7065
|
+
}
|
|
7066
|
+
async function migratePromptManifest(options = {}) {
|
|
7067
|
+
const root = process.cwd();
|
|
7068
|
+
const manifestPath = resolve4(root, MANIFEST_PATH);
|
|
7069
|
+
if (!existsSync2(manifestPath)) {
|
|
7070
|
+
throw new CliInputError(`No ${MANIFEST_PATH} to migrate in ${root}.`, "Run `moda prompts init` to create a version 2 manifest.");
|
|
7071
|
+
}
|
|
7072
|
+
const original = readFileSync(manifestPath, "utf8");
|
|
7073
|
+
const manifest = readManifest(root);
|
|
7074
|
+
const inspection = inspectPromptManifest(root);
|
|
7075
|
+
const addedPaths = inspection.legacyOnlyFiles;
|
|
7076
|
+
const addedPatterns = addedPaths.map((path) => import_fast_glob.default.escapePath(path));
|
|
7077
|
+
const promptPaths = [...manifest.promptPaths, ...addedPatterns];
|
|
7078
|
+
let sources = manifest.sources;
|
|
7079
|
+
let addedSources = [];
|
|
7080
|
+
let pendingSources = [];
|
|
7081
|
+
if (options.discover) {
|
|
7082
|
+
const { analyzeHarnessPrompts } = await import("./harness-sd44jsaw.js");
|
|
7083
|
+
const result = await analyzeHarnessPrompts({
|
|
7084
|
+
rootDir: root,
|
|
7085
|
+
existing: manifest.sources,
|
|
7086
|
+
flags: { analyst: "local-scan" },
|
|
7087
|
+
env: options.env ?? process.env,
|
|
7088
|
+
dryRun: true
|
|
7089
|
+
});
|
|
7090
|
+
const negations = promptPaths.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
|
|
7091
|
+
const claimed = new Set([
|
|
7092
|
+
...globPromptFiles(root, promptPaths.filter((pattern) => !pattern.startsWith("!"))),
|
|
7093
|
+
...globPromptFiles(root, negations)
|
|
7094
|
+
]);
|
|
7095
|
+
const existingKeys = new Set(sources.map((source) => source.key));
|
|
7096
|
+
const found = result.sources.filter((source) => !existingKeys.has(source.key) && !claimed.has(repoPath(root, source.path)));
|
|
7097
|
+
sources = mergePromptSources(sources, found);
|
|
7098
|
+
addedSources = found.map((source) => source.key).sort();
|
|
7099
|
+
pendingSources = (result.pending ?? []).filter((candidate) => !existingKeys.has(candidate.source.key) && !claimed.has(repoPath(root, candidate.source.path))).map((candidate) => ({
|
|
7100
|
+
key: candidate.source.key,
|
|
7101
|
+
path: candidate.source.path,
|
|
7102
|
+
...candidate.source.selector?.symbol ? { symbol: candidate.source.selector.symbol } : {},
|
|
7103
|
+
reason: candidate.reason ?? "Awaiting independent semantic verification"
|
|
7104
|
+
})).sort((a, b) => a.key.localeCompare(b.key));
|
|
7105
|
+
}
|
|
7106
|
+
try {
|
|
7107
|
+
discoverManifestPrompts({ version: 2, promptPaths, sources });
|
|
7108
|
+
} catch (error) {
|
|
7109
|
+
throw new CliInputError(`Cannot migrate ${MANIFEST_PATH}: ${error instanceof Error ? error.message : String(error)}`, `Give the conflicting prompt a unique \`key:\` (frontmatter or ${MANIFEST_PATH} sources), then rerun \`moda prompts migrate --dry-run\`. Nothing was written.`);
|
|
7110
|
+
}
|
|
7111
|
+
const expected = stableJson({ version: 2, prompt_paths: promptPaths, sources });
|
|
7112
|
+
const serialize = (inPlace) => {
|
|
7113
|
+
const doc = $parseDocument(original, { uniqueKeys: true });
|
|
7114
|
+
doc.set("version", 2);
|
|
7115
|
+
const pathsNode = doc.get("prompt_paths", true);
|
|
7116
|
+
if (inPlace && $isSeq(pathsNode)) {
|
|
7117
|
+
for (const pattern of addedPatterns)
|
|
7118
|
+
pathsNode.items.push(doc.createNode(pattern));
|
|
7119
|
+
} else {
|
|
7120
|
+
doc.set("prompt_paths", promptPaths);
|
|
7121
|
+
}
|
|
7122
|
+
const existingKeys = new Set(manifest.sources.map((source) => source.key));
|
|
7123
|
+
const newSources = sources.filter((source) => !existingKeys.has(source.key));
|
|
7124
|
+
const sourcesNode = doc.get("sources", true);
|
|
7125
|
+
if (inPlace && $isSeq(sourcesNode)) {
|
|
7126
|
+
for (const source of newSources)
|
|
7127
|
+
sourcesNode.items.push(doc.createNode(source));
|
|
7128
|
+
} else if (!inPlace || newSources.length > 0 || sourcesNode === undefined) {
|
|
7129
|
+
doc.set("sources", sources);
|
|
7130
|
+
}
|
|
7131
|
+
return doc.toString();
|
|
7132
|
+
};
|
|
7133
|
+
const matchesExpected = (text) => {
|
|
7134
|
+
const parsed = $parseDocument(text, { uniqueKeys: true }).toJS({ maxAliasCount: 20 }) ?? {};
|
|
7135
|
+
return stableJson({ version: parsed.version, prompt_paths: parsed.prompt_paths ?? [], sources: parsed.sources ?? [] }) === expected;
|
|
7136
|
+
};
|
|
7137
|
+
let content = serialize(true);
|
|
7138
|
+
if (!matchesExpected(content))
|
|
7139
|
+
content = serialize(false);
|
|
7140
|
+
if (!matchesExpected(content)) {
|
|
7141
|
+
throw new CliInputError(`Cannot migrate ${MANIFEST_PATH}: the rewritten manifest does not round-trip (shared YAML anchors or aliases?). Nothing was written.`, `Replace anchors/aliases in prompt_paths and sources with plain lists, then rerun \`moda prompts migrate --dry-run\`.`);
|
|
7142
|
+
}
|
|
7143
|
+
const changed = content !== original;
|
|
7144
|
+
if (!options.dryRun && changed) {
|
|
7145
|
+
if (hashFile(MANIFEST_PATH) !== sha256(original))
|
|
7146
|
+
throw new Error(`${MANIFEST_PATH} changed during migration. Retry.`);
|
|
7147
|
+
atomicWrite(MANIFEST_PATH, content);
|
|
7148
|
+
}
|
|
7149
|
+
return {
|
|
7150
|
+
manifest: MANIFEST_PATH,
|
|
7151
|
+
manifestPath,
|
|
7152
|
+
dryRun: options.dryRun === true,
|
|
7153
|
+
changed,
|
|
7154
|
+
fromVersion: manifest.version ?? 2,
|
|
7155
|
+
toVersion: 2,
|
|
7156
|
+
addedPaths,
|
|
7157
|
+
addedSources,
|
|
7158
|
+
pendingSources,
|
|
7159
|
+
zeroMatchPatterns: inspection.zeroMatchPatterns,
|
|
7160
|
+
promptPaths,
|
|
7161
|
+
content
|
|
7162
|
+
};
|
|
7163
|
+
}
|
|
6937
7164
|
function assertContainedSource(root, path) {
|
|
6938
7165
|
const abs = resolve4(root, path);
|
|
6939
7166
|
const rel = relative(realpathSync(root), realpathSync(abs));
|
|
@@ -6987,18 +7214,6 @@ function parsePromptFile(absPath) {
|
|
|
6987
7214
|
});
|
|
6988
7215
|
}
|
|
6989
7216
|
function normalizePrompt(prompt) {
|
|
6990
|
-
const normalized = stableJson({
|
|
6991
|
-
key: prompt.key,
|
|
6992
|
-
content: prompt.content,
|
|
6993
|
-
systemPrompt: prompt.systemPrompt ?? null,
|
|
6994
|
-
messages: prompt.messages ?? [],
|
|
6995
|
-
variables: prompt.variables ?? [],
|
|
6996
|
-
variablesSchema: prompt.variablesSchema ?? {},
|
|
6997
|
-
modelConfig: prompt.modelConfig ?? {},
|
|
6998
|
-
toolRefs: prompt.toolRefs ?? [],
|
|
6999
|
-
responseSchema: prompt.responseSchema ?? null,
|
|
7000
|
-
...prompt.sourceDefinition ? { sourceDefinition: promptSourceHashPayload(prompt.sourceDefinition) } : {}
|
|
7001
|
-
});
|
|
7002
7217
|
return {
|
|
7003
7218
|
key: prompt.key,
|
|
7004
7219
|
name: prompt.name || prompt.key,
|
|
@@ -7013,28 +7228,14 @@ function normalizePrompt(prompt) {
|
|
|
7013
7228
|
modelConfig: prompt.modelConfig,
|
|
7014
7229
|
toolRefs: prompt.toolRefs,
|
|
7015
7230
|
responseSchema: prompt.responseSchema,
|
|
7016
|
-
contentHash:
|
|
7231
|
+
contentHash: computePromptContentHash(prompt)
|
|
7017
7232
|
};
|
|
7018
7233
|
}
|
|
7019
|
-
function
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
sourceHash: definition.sourceHash
|
|
7025
|
-
} : ["builder", "template"].includes(definition.kind) ? {
|
|
7026
|
-
sourceHash: definition.sourceHash,
|
|
7027
|
-
dependencies: definition.dependencies,
|
|
7028
|
-
coverage: definition.coverage,
|
|
7029
|
-
unresolved: definition.unresolved
|
|
7030
|
-
} : {}
|
|
7031
|
-
};
|
|
7032
|
-
}
|
|
7033
|
-
function readManifest() {
|
|
7034
|
-
if (!existsSync2(MANIFEST_PATH))
|
|
7035
|
-
return { promptPaths: DEFAULT_PROMPT_PATHS, sources: [] };
|
|
7036
|
-
assertContainedSource(process.cwd(), MANIFEST_PATH);
|
|
7037
|
-
const doc = $parseDocument(readFileSync(MANIFEST_PATH, "utf8"), { uniqueKeys: true });
|
|
7234
|
+
function readManifest(root = process.cwd()) {
|
|
7235
|
+
if (!existsSync2(resolve4(root, MANIFEST_PATH)))
|
|
7236
|
+
return { version: null, promptPaths: DEFAULT_PROMPT_PATHS, sources: [] };
|
|
7237
|
+
assertContainedSource(root, MANIFEST_PATH);
|
|
7238
|
+
const doc = $parseDocument(readFileSync(resolve4(root, MANIFEST_PATH), "utf8"), { uniqueKeys: true });
|
|
7038
7239
|
if (doc.errors.length)
|
|
7039
7240
|
throw new Error(`Invalid ${MANIFEST_PATH}: ${doc.errors[0].message}`);
|
|
7040
7241
|
const parsed = doc.toJS({ maxAliasCount: 20 });
|
|
@@ -7055,33 +7256,96 @@ function readManifest() {
|
|
|
7055
7256
|
throw new Error(`Duplicate prompt source key '${source.key}'.`);
|
|
7056
7257
|
seen.add(source.key);
|
|
7057
7258
|
}
|
|
7058
|
-
return { promptPaths: parsed.prompt_paths ?? (parsed.version === 1 ? DEFAULT_PROMPT_PATHS : []), sources };
|
|
7259
|
+
return { version: parsed.version, promptPaths: parsed.prompt_paths ?? (parsed.version === 1 ? DEFAULT_PROMPT_PATHS : []), sources };
|
|
7059
7260
|
}
|
|
7261
|
+
var HARNESS_REANALYZE_COMMAND = "moda harness analyze --experimental --concern=prompts";
|
|
7060
7262
|
function readHarnessSources(reportPath) {
|
|
7061
7263
|
const selected = reportPath === "true" ? ".moda/harness-report.json" : reportPath;
|
|
7062
7264
|
assertContainedSource(process.cwd(), selected);
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7265
|
+
let raw;
|
|
7266
|
+
try {
|
|
7267
|
+
raw = JSON.parse(readFileSync(resolve4(selected), "utf8"));
|
|
7268
|
+
} catch (error) {
|
|
7269
|
+
throw new CliInputError(`Could not read harness report ${selected}: ${error instanceof Error ? error.message : String(error)}.`, `Regenerate it with \`${HARNESS_REANALYZE_COMMAND}\`.`);
|
|
7270
|
+
}
|
|
7271
|
+
const record = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
7272
|
+
const graph = record.graph && typeof record.graph === "object" ? record.graph : undefined;
|
|
7273
|
+
const schemaVersion = typeof record.schemaVersion === "string" ? record.schemaVersion : typeof graph?.schemaVersion === "string" ? graph.schemaVersion : typeof graph?.graphSchemaVersion === "string" ? graph.graphSchemaVersion : null;
|
|
7274
|
+
const schemaLabel = schemaVersion ?? "no schemaVersion";
|
|
7275
|
+
if (!graph) {
|
|
7276
|
+
throw new CliInputError(`${selected} is not a harness report with a graph (schema: ${schemaLabel}). Regenerate it with \`${HARNESS_REANALYZE_COMMAND}\`.`);
|
|
7277
|
+
}
|
|
7278
|
+
const report = normalizeHarnessReport(raw);
|
|
7279
|
+
const artifacts = (report.graph.artifacts ?? []).filter((artifact) => artifact.type === "prompt");
|
|
7067
7280
|
const sources = [];
|
|
7281
|
+
const skipped = [];
|
|
7282
|
+
const tracked = [];
|
|
7283
|
+
const root = process.cwd();
|
|
7284
|
+
const manifest = readManifest(root);
|
|
7285
|
+
const negations = manifest.promptPaths.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
|
|
7286
|
+
const globbed = new Set(globPromptFiles(root, manifest.promptPaths.filter((pattern) => !pattern.startsWith("!")), negations));
|
|
7287
|
+
const ownedKeys = new Map(manifest.sources.map((source) => [repoPath(root, source.path), source.key]));
|
|
7068
7288
|
for (const artifact of artifacts) {
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7289
|
+
const label = String(artifact.name ?? artifact.id ?? "unnamed prompt");
|
|
7290
|
+
let mapping;
|
|
7291
|
+
try {
|
|
7292
|
+
mapping = backfillPromptSource(process.cwd(), artifact);
|
|
7293
|
+
} catch (error) {
|
|
7294
|
+
skipped.push({ artifact: label, reason: `source mapping could not be derived: ${error instanceof Error ? error.message : String(error)}` });
|
|
7295
|
+
continue;
|
|
7296
|
+
}
|
|
7297
|
+
if (!mapping) {
|
|
7298
|
+
skipped.push({
|
|
7299
|
+
artifact: label,
|
|
7300
|
+
reason: artifact.sourcePath ? `no promptSource, and no single definition in ${artifact.sourcePath} matches its name` : "no promptSource or sourcePath (legacy body snippets cannot be registered)"
|
|
7301
|
+
});
|
|
7302
|
+
continue;
|
|
7303
|
+
}
|
|
7304
|
+
if (!artifact.promptSource && !mapping.selector) {
|
|
7305
|
+
const path = repoPath(root, mapping.path);
|
|
7306
|
+
const nativeFile = /\.prompt\.(md|json)$/i.test(path);
|
|
7307
|
+
const ownedKey = ownedKeys.get(path);
|
|
7308
|
+
if (ownedKey !== undefined || globbed.has(path)) {
|
|
7309
|
+
let key = ownedKey ?? keyFromPath(path);
|
|
7310
|
+
try {
|
|
7311
|
+
if (ownedKey === undefined && nativeFile)
|
|
7312
|
+
key = parsePromptFile(resolve4(root, path)).key;
|
|
7313
|
+
} catch {}
|
|
7314
|
+
tracked.push({ artifact: label, key, path });
|
|
7315
|
+
continue;
|
|
7316
|
+
}
|
|
7317
|
+
if (nativeFile) {
|
|
7318
|
+
skipped.push({
|
|
7319
|
+
artifact: label,
|
|
7320
|
+
reason: `${path} is a dedicated prompt file; add it to prompt_paths (\`moda prompts migrate\` adds files older CLIs discovered) so it keeps its own key and parser`
|
|
7321
|
+
});
|
|
7322
|
+
continue;
|
|
7323
|
+
}
|
|
7324
|
+
if (/\.prompt\.ya?ml$/i.test(path))
|
|
7325
|
+
mapping = { ...mapping, key: keyFromPath(path) };
|
|
7326
|
+
}
|
|
7327
|
+
try {
|
|
7328
|
+
sources.push(resolvePromptSource(process.cwd(), mapping).sourceDefinition.source);
|
|
7329
|
+
} catch (error) {
|
|
7330
|
+
skipped.push({ artifact: label, reason: error instanceof Error ? error.message : String(error) });
|
|
7331
|
+
}
|
|
7332
|
+
}
|
|
7333
|
+
if (!sources.length && !tracked.length) {
|
|
7334
|
+
const detail = skipped.length ? ` ${skipped.length} prompt artifact(s) skipped: ${skipped.slice(0, 5).map((skip) => `'${skip.artifact}' (${skip.reason})`).join("; ")}${skipped.length > 5 ? "; ..." : ""}.` : " It contains no prompt artifacts.";
|
|
7335
|
+
throw new CliInputError(`Harness report ${selected} (schema: ${schemaLabel}) has no prompt that maps to current source.${detail} Regenerate it with \`${HARNESS_REANALYZE_COMMAND}\`.`, `Or map prompts directly under \`sources:\` in ${MANIFEST_PATH}.`);
|
|
7073
7336
|
}
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7337
|
+
return { sources, result: { report: selected, schemaVersion, registered: sources.map((source) => source.key), skipped, tracked } };
|
|
7338
|
+
}
|
|
7339
|
+
function harnessSkipWarning(result) {
|
|
7340
|
+
return `${result.skipped.length} harness prompt(s) skipped (registered ${result.registered.length}): ${result.skipped.slice(0, 3).map((skip) => `'${skip.artifact}' (${skip.reason})`).join("; ")}${result.skipped.length > 3 ? "; ..." : ""}. See harnessImport.skipped; regenerate the report with \`${HARNESS_REANALYZE_COMMAND}\` to map them.`;
|
|
7077
7341
|
}
|
|
7078
7342
|
function importHarnessPromptSources(reportPath = ".moda/harness-report.json") {
|
|
7079
|
-
const sources = readHarnessSources(reportPath);
|
|
7343
|
+
const { sources, result } = readHarnessSources(reportPath);
|
|
7080
7344
|
const manifest = readManifest();
|
|
7081
7345
|
const merged = mergePromptSources(manifest.sources, sources);
|
|
7082
7346
|
discoverPrompts(sources);
|
|
7083
7347
|
atomicWrite(MANIFEST_PATH, $stringify({ version: 2, prompt_paths: manifest.promptPaths, sources: merged }));
|
|
7084
|
-
return
|
|
7348
|
+
return result;
|
|
7085
7349
|
}
|
|
7086
7350
|
function mergePromptSources(existingSources, sources) {
|
|
7087
7351
|
const merged = new Map(existingSources.map((source) => [source.key, source]));
|
|
@@ -7174,17 +7438,6 @@ function getGitCommit() {
|
|
|
7174
7438
|
return;
|
|
7175
7439
|
}
|
|
7176
7440
|
}
|
|
7177
|
-
function stableJson(value) {
|
|
7178
|
-
return JSON.stringify(sortJson(value));
|
|
7179
|
-
}
|
|
7180
|
-
function sortJson(value) {
|
|
7181
|
-
if (Array.isArray(value))
|
|
7182
|
-
return value.map(sortJson);
|
|
7183
|
-
if (value && typeof value === "object") {
|
|
7184
|
-
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sortJson(item)]));
|
|
7185
|
-
}
|
|
7186
|
-
return value;
|
|
7187
|
-
}
|
|
7188
7441
|
function sha256(input) {
|
|
7189
7442
|
return createHash("sha256").update(input).digest("hex");
|
|
7190
7443
|
}
|
|
@@ -7304,6 +7557,9 @@ function renderFixesBlock() {
|
|
|
7304
7557
|
"- Propose the change in the code, prompt, tool definition, or skill that",
|
|
7305
7558
|
" causes the issue, and cite the Moda problem or trace ids it is based on",
|
|
7306
7559
|
" in the PR description.",
|
|
7560
|
+
"- Prompts live in `.moda/prompts.yml` (version 2: `prompt_paths` globs plus",
|
|
7561
|
+
" `sources:` mappings for prompts defined in code); after `moda prompts sync`,",
|
|
7562
|
+
" pass `prompt: lock.prompts['<key>']` to createThinkTelemetry/createAI7Telemetry.",
|
|
7307
7563
|
"- After the fix ships, record it with",
|
|
7308
7564
|
" `moda problem-feedback <problem_id> --action=mark_fixed`; if the issue",
|
|
7309
7565
|
" is detected again, the Problem comes back.",
|
|
@@ -8315,10 +8571,7 @@ async function buildDoctorReport(context, options) {
|
|
|
8315
8571
|
});
|
|
8316
8572
|
checks.push(harnessCurrentCheck(context.cwd, manifestPath, manifestExists, tenantId));
|
|
8317
8573
|
checks.push(harnessSyncStateCheck(context.cwd));
|
|
8318
|
-
checks.push(
|
|
8319
|
-
"prompts",
|
|
8320
|
-
".moda/prompts.yml"
|
|
8321
|
-
], "moda prompts init"));
|
|
8574
|
+
checks.push(promptsDiscoveredCheck(context.cwd));
|
|
8322
8575
|
checks.push(discoveryCheck("skills_discovered", "harness", "Skills are discovered", context.cwd, [
|
|
8323
8576
|
"skills",
|
|
8324
8577
|
".claude/skills",
|
|
@@ -8588,6 +8841,56 @@ function harnessSyncStateCheck(cwd) {
|
|
|
8588
8841
|
]
|
|
8589
8842
|
};
|
|
8590
8843
|
}
|
|
8844
|
+
function promptsDiscoveredCheck(cwd) {
|
|
8845
|
+
const base = { id: "prompts_discovered", group: "harness", title: "Prompts are discovered" };
|
|
8846
|
+
if (!existsSync5(join4(cwd, ".moda", "prompts.yml"))) {
|
|
8847
|
+
return discoveryCheck(base.id, base.group, base.title, cwd, ["prompts", ".moda/prompts.yml"], "moda prompts init");
|
|
8848
|
+
}
|
|
8849
|
+
let inspection;
|
|
8850
|
+
try {
|
|
8851
|
+
inspection = inspectPromptManifest(cwd);
|
|
8852
|
+
} catch (error) {
|
|
8853
|
+
return {
|
|
8854
|
+
...base,
|
|
8855
|
+
status: "fail",
|
|
8856
|
+
severity: "warning",
|
|
8857
|
+
evidence: [{ label: "manifest", value: ".moda/prompts.yml" }, { label: "error", value: error instanceof Error ? error.message : String(error) }],
|
|
8858
|
+
remediation: "Fix .moda/prompts.yml (version, prompt_paths, sources).",
|
|
8859
|
+
nextCommand: "moda prompts status"
|
|
8860
|
+
};
|
|
8861
|
+
}
|
|
8862
|
+
const evidence = [
|
|
8863
|
+
{ label: "manifest", value: `.moda/prompts.yml (version ${inspection.manifestVersion})` },
|
|
8864
|
+
{ label: "matched_files", value: String(inspection.matchedFiles) },
|
|
8865
|
+
{ label: "sources", value: String(inspection.sources) },
|
|
8866
|
+
...inspection.zeroMatchPatterns.length > 0 ? [{ label: "zero_match_patterns", value: inspection.zeroMatchPatterns.join(", ") }] : [],
|
|
8867
|
+
...inspection.legacyOnlyFiles.length > 0 ? [{
|
|
8868
|
+
label: "legacy_only_files",
|
|
8869
|
+
value: `${inspection.legacyOnlyFiles.length} (${inspection.legacyOnlyFiles.slice(0, 3).join(", ")}${inspection.legacyOnlyFiles.length > 3 ? ", ..." : ""})`
|
|
8870
|
+
}] : []
|
|
8871
|
+
];
|
|
8872
|
+
if (inspection.legacyOnlyFiles.length > 0) {
|
|
8873
|
+
return {
|
|
8874
|
+
...base,
|
|
8875
|
+
status: "warning",
|
|
8876
|
+
severity: "warning",
|
|
8877
|
+
evidence,
|
|
8878
|
+
remediation: "Prompt files found by older CLIs no longer match the manifest globs (they are exact paths from the repository root). Migrate the manifest to keep them tracked under their existing keys.",
|
|
8879
|
+
nextCommand: "moda prompts migrate --dry-run"
|
|
8880
|
+
};
|
|
8881
|
+
}
|
|
8882
|
+
if (inspection.matchedFiles === 0 && inspection.sources === 0) {
|
|
8883
|
+
return {
|
|
8884
|
+
...base,
|
|
8885
|
+
status: "warning",
|
|
8886
|
+
severity: "warning",
|
|
8887
|
+
evidence,
|
|
8888
|
+
remediation: "The prompt manifest matches no files and maps no sources, so sync registers nothing. Fix prompt_paths or preview source discovery.",
|
|
8889
|
+
nextCommand: "moda prompts sync --dry-run"
|
|
8890
|
+
};
|
|
8891
|
+
}
|
|
8892
|
+
return { ...base, status: "pass", severity: "info", evidence };
|
|
8893
|
+
}
|
|
8591
8894
|
function discoveryCheck(id, group, title, cwd, relativePaths, nextCommand) {
|
|
8592
8895
|
const found = relativePaths.filter((path) => existsSync5(join4(cwd, path)));
|
|
8593
8896
|
return {
|
|
@@ -8884,7 +9187,7 @@ function agentPurposeForCommand(command) {
|
|
|
8884
9187
|
return "Inspect or repair Moda setup.";
|
|
8885
9188
|
}
|
|
8886
9189
|
function stableGraphHash(graph) {
|
|
8887
|
-
return createHash3("sha256").update(JSON.stringify(
|
|
9190
|
+
return createHash3("sha256").update(JSON.stringify(sortJson({
|
|
8888
9191
|
...graph,
|
|
8889
9192
|
scan: {
|
|
8890
9193
|
...graph.scan,
|
|
@@ -8894,11 +9197,11 @@ function stableGraphHash(graph) {
|
|
|
8894
9197
|
}
|
|
8895
9198
|
}))).digest("hex");
|
|
8896
9199
|
}
|
|
8897
|
-
function
|
|
9200
|
+
function sortJson(value) {
|
|
8898
9201
|
if (Array.isArray(value))
|
|
8899
|
-
return value.map((item) =>
|
|
9202
|
+
return value.map((item) => sortJson(item));
|
|
8900
9203
|
if (value && typeof value === "object") {
|
|
8901
|
-
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key,
|
|
9204
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sortJson(item)]));
|
|
8902
9205
|
}
|
|
8903
9206
|
return value;
|
|
8904
9207
|
}
|