@moda-ai/cli 1.52.0 → 1.55.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-he2wb4hd.js → cli-9250ckvg.js} +18 -14
- package/dist/{cli-ha40mgy1.js → cli-a2jq9yd5.js} +384 -81
- package/dist/cli-mm48br00.js +47 -0
- package/dist/cli.js +62 -13
- package/dist/{harness-github-actions-gbty3k66.js → harness-github-actions-075xzdvp.js} +1 -1
- package/dist/{harness-s13wvdxt.js → harness-sd44jsaw.js} +1 -1
- package/dist/{index-pykaaezb.js → index-hn9t95tr.js} +3 -2
- package/dist/prompt-hash.d.ts +36 -0
- package/dist/prompt-hash.js +15 -0
- package/dist/prompt-source-mcp.js +1 -1
- 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 +61 -6
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/prompt-hash.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
var PROMPT_HASH_VERSION = 1;
|
|
4
|
+
function promptSourceHashPayload(definition) {
|
|
5
|
+
return {
|
|
6
|
+
kind: definition.kind,
|
|
7
|
+
source: definition.source,
|
|
8
|
+
...["text", "messages"].includes(definition.kind) ? {
|
|
9
|
+
sourceHash: definition.sourceHash
|
|
10
|
+
} : ["builder", "template"].includes(definition.kind) ? {
|
|
11
|
+
sourceHash: definition.sourceHash,
|
|
12
|
+
dependencies: definition.dependencies,
|
|
13
|
+
coverage: definition.coverage,
|
|
14
|
+
unresolved: definition.unresolved
|
|
15
|
+
} : {}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function stableJson(value) {
|
|
19
|
+
return JSON.stringify(sortJson(value));
|
|
20
|
+
}
|
|
21
|
+
function sortJson(value) {
|
|
22
|
+
if (Array.isArray(value))
|
|
23
|
+
return value.map(sortJson);
|
|
24
|
+
if (value && typeof value === "object") {
|
|
25
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, sortJson(item)]));
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function promptContentHashPayload(prompt) {
|
|
30
|
+
return stableJson({
|
|
31
|
+
key: prompt.key,
|
|
32
|
+
content: prompt.content,
|
|
33
|
+
systemPrompt: prompt.systemPrompt ?? null,
|
|
34
|
+
messages: prompt.messages ?? [],
|
|
35
|
+
variables: prompt.variables ?? [],
|
|
36
|
+
variablesSchema: prompt.variablesSchema ?? {},
|
|
37
|
+
modelConfig: prompt.modelConfig ?? {},
|
|
38
|
+
toolRefs: prompt.toolRefs ?? [],
|
|
39
|
+
responseSchema: prompt.responseSchema ?? null,
|
|
40
|
+
...prompt.sourceDefinition ? { sourceDefinition: promptSourceHashPayload(prompt.sourceDefinition) } : {}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function computePromptContentHash(prompt) {
|
|
44
|
+
return createHash("sha256").update(promptContentHashPayload(prompt)).digest("hex");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { PROMPT_HASH_VERSION, promptSourceHashPayload, stableJson, promptContentHashPayload, computePromptContentHash };
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
runPromptsCommand,
|
|
9
9
|
runSkillsCommand,
|
|
10
10
|
runStatusCommand
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-a2jq9yd5.js";
|
|
12
|
+
import"./cli-mm48br00.js";
|
|
12
13
|
import {
|
|
13
14
|
ApiError,
|
|
14
15
|
HARNESS_REPORT_APPROVAL_PATH,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
terminalStyles,
|
|
38
39
|
unknownSignalsSubcommandError,
|
|
39
40
|
validateHarnessReport
|
|
40
|
-
} from "./cli-
|
|
41
|
+
} from "./cli-9250ckvg.js";
|
|
41
42
|
import"./cli-ssfq84k5.js";
|
|
42
43
|
import {
|
|
43
44
|
authFetch,
|
|
@@ -409,6 +410,7 @@ var SearchSchema = z.object({
|
|
|
409
410
|
q: z.string().min(1).max(500),
|
|
410
411
|
mode: z.enum(SEARCH_MODES).default("hybrid").optional(),
|
|
411
412
|
user_id: z.string().optional(),
|
|
413
|
+
conversation_id: z.string().min(1).max(256).optional(),
|
|
412
414
|
time_range: z.enum(TIME_RANGES).default("all").optional(),
|
|
413
415
|
limit: z.number().min(1).max(100).default(20).optional(),
|
|
414
416
|
include_tool_io: boolFlag(),
|
|
@@ -2000,6 +2002,20 @@ function nextCommandsFor(fix) {
|
|
|
2000
2002
|
}
|
|
2001
2003
|
];
|
|
2002
2004
|
case "UNROUTED":
|
|
2005
|
+
return [
|
|
2006
|
+
{
|
|
2007
|
+
command: `moda fix ${fix.id} --packet`,
|
|
2008
|
+
purpose: "Read the investigation packet — this fix is deliverable as diagnosis.",
|
|
2009
|
+
mutability: "read",
|
|
2010
|
+
requires_approval: false
|
|
2011
|
+
},
|
|
2012
|
+
{
|
|
2013
|
+
command: "moda prompts status",
|
|
2014
|
+
purpose: "If the reason says evidence carries no prompt version: check the prompt lockfile, then pass prompt: lock.prompts['<key>'] to createThinkTelemetry / createAI7Telemetry (docs.moda.dev/prompt-management/attribution).",
|
|
2015
|
+
mutability: "read",
|
|
2016
|
+
requires_approval: false
|
|
2017
|
+
}
|
|
2018
|
+
];
|
|
2003
2019
|
case "NO_EVIDENCE":
|
|
2004
2020
|
case "BLOCKED_COVERAGE":
|
|
2005
2021
|
case "NO_HEADROOM":
|
|
@@ -7963,9 +7979,10 @@ Commands:
|
|
|
7963
7979
|
--signal=conversations = --signal=traces. <conversation_id> is the trace ID.
|
|
7964
7980
|
|
|
7965
7981
|
Prompt management:
|
|
7966
|
-
prompts init Create .moda/prompts.yml
|
|
7967
|
-
prompts status Show local prompt changes
|
|
7982
|
+
prompts init Create .moda/prompts.yml (version 2: prompt_paths + sources)
|
|
7983
|
+
prompts status Show local prompt changes; warns on globs that match nothing
|
|
7968
7984
|
prompts diff Only changed/new/deleted prompts since last sync
|
|
7985
|
+
prompts migrate [--dry-run] Rewrite a version 1 .moda/prompts.yml to version 2
|
|
7969
7986
|
prompts sync Analyze prompt sources and upload versions
|
|
7970
7987
|
prompts sync --watch Upload again when prompt files change
|
|
7971
7988
|
prompts list|show|proposals|usage|replay-runs|decide
|
|
@@ -8033,6 +8050,7 @@ Traces flags:
|
|
|
8033
8050
|
Search flags:
|
|
8034
8051
|
--mode=keyword|semantic|hybrid Search mode (default hybrid)
|
|
8035
8052
|
--user-id=ID Filter to a single user
|
|
8053
|
+
--conversation-id=ID Search inside one trace only
|
|
8036
8054
|
--time-range=RANGE Time range filter (all|1h|3d|7d|24h|30d|90d)
|
|
8037
8055
|
--limit=N Maximum results to return (1-100, default 20)
|
|
8038
8056
|
|
|
@@ -8792,6 +8810,23 @@ function shellArg(value) {
|
|
|
8792
8810
|
return `"${value}"`;
|
|
8793
8811
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
8794
8812
|
}
|
|
8813
|
+
function searchCoverageWarnings(coverage, timeRange) {
|
|
8814
|
+
const record = asRecord(coverage);
|
|
8815
|
+
if (!record)
|
|
8816
|
+
return [];
|
|
8817
|
+
const warnings = [];
|
|
8818
|
+
const freshness = asRecord(record.index_freshness);
|
|
8819
|
+
const originalUsed = record.original_messages_search === "used" || record.keyword_source === "original_messages";
|
|
8820
|
+
if (freshness?.window_extends_beyond_index === true) {
|
|
8821
|
+
const indexedAt = asString(freshness.latest_indexed_message_at) ?? "none in the last 7 days";
|
|
8822
|
+
const originalAt = asString(freshness.latest_original_message_at) ?? "unknown";
|
|
8823
|
+
warnings.push(`The search index trails ingestion for --time-range=${timeRange}: newest indexed message ${indexedAt}, newest message ${originalAt}. ` + (originalUsed ? "Literal matches were read from the original messages, but semantic ranking cannot see messages after the index cutoff." : "Messages after the index cutoff are missing from these results; retry with --mode=keyword for exact text."));
|
|
8824
|
+
}
|
|
8825
|
+
if (record.results_only_from_original_messages === true) {
|
|
8826
|
+
warnings.push("Every result came from the original-message fallback: the search index returned no literal match (it may lag ingestion or not tokenize this text). Scores are literal match counts.");
|
|
8827
|
+
}
|
|
8828
|
+
return warnings;
|
|
8829
|
+
}
|
|
8795
8830
|
async function searchFallbackToTraces(params) {
|
|
8796
8831
|
const limit = Math.min(params.limit ?? 20, 100);
|
|
8797
8832
|
const offset = params.offset ?? 0;
|
|
@@ -9338,7 +9373,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
9338
9373
|
}
|
|
9339
9374
|
case "search": {
|
|
9340
9375
|
if (!positional || positional.trim() === "") {
|
|
9341
|
-
throw new CliInputError("<query> is required", 'Usage: moda search "<query>" [--mode=keyword|semantic|hybrid] [--user-id=ID] [--time-range=RANGE] [--include-tool-io] [--limit=N]');
|
|
9376
|
+
throw new CliInputError("<query> is required", 'Usage: moda search "<query>" [--mode=keyword|semantic|hybrid] [--user-id=ID] [--conversation-id=ID] [--time-range=RANGE] [--include-tool-io] [--limit=N]');
|
|
9342
9377
|
}
|
|
9343
9378
|
const args = withStringIdFlags(flagsToArgs(flags, "q", positional), flags);
|
|
9344
9379
|
const params = parseCommandFlags(SearchSchema, args, command, { positionalKey: "q" });
|
|
@@ -9348,6 +9383,8 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
9348
9383
|
query2.set("mode", params.mode);
|
|
9349
9384
|
if (params.user_id)
|
|
9350
9385
|
query2.set("user_id", params.user_id);
|
|
9386
|
+
if (params.conversation_id)
|
|
9387
|
+
query2.set("conversation_id", params.conversation_id);
|
|
9351
9388
|
if (params.time_range)
|
|
9352
9389
|
query2.set("time_range", params.time_range);
|
|
9353
9390
|
if (params.limit)
|
|
@@ -9365,11 +9402,12 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
9365
9402
|
} else if (params.mode && actualMode && actualMode !== params.mode && params.mode !== "hybrid") {
|
|
9366
9403
|
warnings.push(`Search ran in ${actualMode} mode instead of the requested ${params.mode} mode.`);
|
|
9367
9404
|
}
|
|
9405
|
+
warnings.push(...searchCoverageWarnings(searchRecord.coverage, params.time_range ?? "all"));
|
|
9368
9406
|
const results = Array.isArray(searchRecord.results) ? searchRecord.results : [];
|
|
9369
9407
|
if (results.length > 0 && (params.offset ?? 0) > 0) {
|
|
9370
9408
|
warnings.push(`--offset=${params.offset} was not applied: message-level search is not pageable (it returns the top --limit hits). ` + "--offset only pages the trace-level fallback. Narrow with --time-range/--user-id or raise --limit instead.");
|
|
9371
9409
|
}
|
|
9372
|
-
if (results.length === 0) {
|
|
9410
|
+
if (results.length === 0 && !params.conversation_id) {
|
|
9373
9411
|
const fallback = await searchFallbackToTraces({ ...params, offset: params.offset });
|
|
9374
9412
|
if (fallback?.kind === "notice") {
|
|
9375
9413
|
warnings.push(fallback.warning);
|
|
@@ -10132,7 +10170,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
10132
10170
|
function telemetryCommandName(definition) {
|
|
10133
10171
|
return definition.telemetryCommand ?? definition.name;
|
|
10134
10172
|
}
|
|
10135
|
-
var OFFLINE_PROMPTS_SUBCOMMANDS = new Set(["init", "status", "diff"]);
|
|
10173
|
+
var OFFLINE_PROMPTS_SUBCOMMANDS = new Set(["init", "status", "diff", "migrate"]);
|
|
10136
10174
|
function legacyCommand(metadata) {
|
|
10137
10175
|
return {
|
|
10138
10176
|
...metadata,
|
|
@@ -10226,7 +10264,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
10226
10264
|
resetApiRequestCountBeforeRun: true,
|
|
10227
10265
|
telemetry: "result",
|
|
10228
10266
|
handler: async (context) => {
|
|
10229
|
-
const { runInit } = await import("./index-
|
|
10267
|
+
const { runInit } = await import("./index-hn9t95tr.js");
|
|
10230
10268
|
if (context.outputMode === "agent-stream") {
|
|
10231
10269
|
context.output.writeEvent({
|
|
10232
10270
|
event: "started",
|
|
@@ -10980,18 +11018,18 @@ var commandRegistry = createCommandRegistry([
|
|
|
10980
11018
|
legacyCommand({
|
|
10981
11019
|
name: "prompts",
|
|
10982
11020
|
description: "Manage prompt versions and labels",
|
|
10983
|
-
examples: ["moda prompts init", "moda prompts status", "moda prompts sync", "moda prompts ab --baseline=old.md --candidate=new.md"],
|
|
11021
|
+
examples: ["moda prompts init", "moda prompts status", "moda prompts migrate --dry-run", "moda prompts sync", "moda prompts ab --baseline=old.md --candidate=new.md"],
|
|
10984
11022
|
subcommands: [
|
|
10985
11023
|
{
|
|
10986
11024
|
name: "init",
|
|
10987
|
-
description: "Create .moda/prompts.yml in the current directory (never overwrites)",
|
|
11025
|
+
description: "Create a version 2 .moda/prompts.yml in the current directory (never overwrites). prompt_paths are exact fast-glob patterns from the repository root; sources: maps prompts defined in code (key, path, selector.symbol|pointer, kind, dependencies).",
|
|
10988
11026
|
usage: "moda prompts init [--from-harness=<report>]",
|
|
10989
|
-
flags: [{ name: "--from-harness=<report>", description: "Register verified source mappings from a harness report; source files stay in place." }],
|
|
11027
|
+
flags: [{ name: "--from-harness=<report>", description: "Register verified source mappings from a harness report (older reports are normalized; prompts without a promptSource are mapped from sourcePath when unambiguous). Unresolvable prompts are returned in imported.skipped with reasons; source files stay in place." }],
|
|
10990
11028
|
examples: ["moda prompts init", "cd /path/to/project && moda prompts init"]
|
|
10991
11029
|
},
|
|
10992
11030
|
{
|
|
10993
11031
|
name: "status",
|
|
10994
|
-
description: "Show prompt files that changed since the last sync",
|
|
11032
|
+
description: "Show prompt files that changed since the last sync. Also reports prompt_paths patterns that match no files (zeroMatchPatterns) and files only older CLIs discovered (legacyOnlyFiles), with a warning pointing at `moda prompts migrate`.",
|
|
10995
11033
|
usage: "moda prompts status",
|
|
10996
11034
|
examples: ["moda prompts status"]
|
|
10997
11035
|
},
|
|
@@ -11001,6 +11039,16 @@ var commandRegistry = createCommandRegistry([
|
|
|
11001
11039
|
usage: "moda prompts diff",
|
|
11002
11040
|
examples: ["moda prompts diff"]
|
|
11003
11041
|
},
|
|
11042
|
+
{
|
|
11043
|
+
name: "migrate",
|
|
11044
|
+
description: "Rewrite .moda/prompts.yml to version 2: keeps every pattern and source, adds explicit paths for prompt files only the older repo-wide discovery matched (so their lockfile keys keep resolving), and ensures a sources: list. Comments are kept; the write is atomic.",
|
|
11045
|
+
usage: "moda prompts migrate [--dry-run] [--discover]",
|
|
11046
|
+
flags: [
|
|
11047
|
+
{ name: "--dry-run", description: "Print the migrated manifest without writing it." },
|
|
11048
|
+
{ name: "--discover", description: "Also scan for code-defined prompts (deterministic, no agent calls). Adds only mappings that need no semantic verification; other candidates are listed under pendingSources with the command that verifies them." }
|
|
11049
|
+
],
|
|
11050
|
+
examples: ["moda prompts migrate --dry-run", "moda prompts migrate", "moda prompts migrate --discover"]
|
|
11051
|
+
},
|
|
11004
11052
|
{
|
|
11005
11053
|
name: "sync",
|
|
11006
11054
|
description: "Analyze prompt sources and upload verified versions to Moda Cloud",
|
|
@@ -11009,7 +11057,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
11009
11057
|
{ name: "--analyst=local-scan|claude|codex|cursor|auto", description: "Defaults to auto: deterministic discovery, installed-agent extension, and independent verification. Use local-scan for no agent calls." },
|
|
11010
11058
|
{ name: "--assist=none", description: "Disable agent fallback, including with an explicit analyst." },
|
|
11011
11059
|
{ name: "--no-analyze", description: "Skip discovery and sync only configured sources." },
|
|
11012
|
-
{ name: "--from-harness=<report>", description: "Read and register harness prompt source mappings before sync." },
|
|
11060
|
+
{ name: "--from-harness=<report>", description: "Read and register harness prompt source mappings before sync; unresolvable prompts are listed in harnessImport.skipped. Fails only when none resolve (regenerate with `moda harness analyze --experimental --concern=prompts`)." },
|
|
11013
11061
|
{ name: "--allow-untrack", description: "Explicitly remove missing keys from local tracking; registry history remains." },
|
|
11014
11062
|
{ name: "--watch", description: "Analyze before syncing; re-upload when mapped prompt files change." },
|
|
11015
11063
|
{ name: "--dry-run", description: "Preview without saving mappings or registry versions; deterministic unless an analyst is explicitly selected." }
|
|
@@ -11485,6 +11533,7 @@ if (isMain) {
|
|
|
11485
11533
|
export {
|
|
11486
11534
|
telemetryCommandName,
|
|
11487
11535
|
stripLoneSurrogates,
|
|
11536
|
+
searchCoverageWarnings,
|
|
11488
11537
|
runCommand,
|
|
11489
11538
|
resolveTailSignal,
|
|
11490
11539
|
printCliError,
|
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
initPrompts,
|
|
4
4
|
runPromptSync,
|
|
5
5
|
runSkillSync
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-a2jq9yd5.js";
|
|
7
|
+
import"./cli-mm48br00.js";
|
|
7
8
|
import {
|
|
8
9
|
codingAgentDisplayName,
|
|
9
10
|
describeCodingAgentEvent,
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
runHarnessCommand,
|
|
23
24
|
startRemoteAnalyze,
|
|
24
25
|
stripAnsi
|
|
25
|
-
} from "./cli-
|
|
26
|
+
} from "./cli-9250ckvg.js";
|
|
26
27
|
import {
|
|
27
28
|
bundledSkillsDir,
|
|
28
29
|
installIntegrationSkill,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Version of the canonicalization below. Bump on ANY change to the hashed payload. */
|
|
2
|
+
export declare const PROMPT_HASH_VERSION: 1;
|
|
3
|
+
/** The typed-source definition fields that participate in the hash. */
|
|
4
|
+
export interface PromptHashSourceDefinition {
|
|
5
|
+
kind: string;
|
|
6
|
+
source: unknown;
|
|
7
|
+
sourceHash?: string;
|
|
8
|
+
dependencies?: unknown;
|
|
9
|
+
coverage?: unknown;
|
|
10
|
+
unresolved?: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** Hashed prompt fields; anything else on the object is ignored. */
|
|
13
|
+
export interface PromptHashInput {
|
|
14
|
+
key: string;
|
|
15
|
+
content: string;
|
|
16
|
+
systemPrompt?: string | null;
|
|
17
|
+
messages?: unknown[] | null;
|
|
18
|
+
variables?: unknown[] | null;
|
|
19
|
+
variablesSchema?: Record<string, unknown> | null;
|
|
20
|
+
modelConfig?: Record<string, unknown> | null;
|
|
21
|
+
toolRefs?: unknown[] | null;
|
|
22
|
+
responseSchema?: Record<string, unknown> | null;
|
|
23
|
+
sourceDefinition?: PromptHashSourceDefinition | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The subset of a typed source definition that is hashed: text/messages hash
|
|
27
|
+
* their extracted source; builders/templates also hash dependency
|
|
28
|
+
* fingerprints and coverage, so a fragment edit mints a new version.
|
|
29
|
+
*/
|
|
30
|
+
export declare function promptSourceHashPayload(definition: PromptHashSourceDefinition): Record<string, unknown>;
|
|
31
|
+
/** JSON.stringify with every object's keys sorted by localeCompare, recursively. */
|
|
32
|
+
export declare function stableJson(value: unknown): string;
|
|
33
|
+
/** The exact string that is hashed (exposed for debugging hash mismatches). */
|
|
34
|
+
export declare function promptContentHashPayload(prompt: PromptHashInput): string;
|
|
35
|
+
/** Lowercase hex SHA-256 of {@link promptContentHashPayload}: the lockfile `contentHash`. */
|
|
36
|
+
export declare function computePromptContentHash(prompt: PromptHashInput): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PROMPT_HASH_VERSION,
|
|
3
|
+
computePromptContentHash,
|
|
4
|
+
promptContentHashPayload,
|
|
5
|
+
promptSourceHashPayload,
|
|
6
|
+
stableJson
|
|
7
|
+
} from "./cli-mm48br00.js";
|
|
8
|
+
import"./cli-0v6na3yp.js";
|
|
9
|
+
export {
|
|
10
|
+
stableJson,
|
|
11
|
+
promptSourceHashPayload,
|
|
12
|
+
promptContentHashPayload,
|
|
13
|
+
computePromptContentHash,
|
|
14
|
+
PROMPT_HASH_VERSION
|
|
15
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moda-ai/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.55.0",
|
|
4
4
|
"description": "CLI for Moda - AI agent analytics and observability",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cli": "./dist/cli.js",
|
|
8
8
|
"moda": "./dist/cli.js"
|
|
9
9
|
},
|
|
10
|
+
"exports": {
|
|
11
|
+
"./prompt-hash": {
|
|
12
|
+
"types": "./dist/prompt-hash.d.ts",
|
|
13
|
+
"import": "./dist/prompt-hash.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json",
|
|
16
|
+
"./*": "./*"
|
|
17
|
+
},
|
|
10
18
|
"files": [
|
|
11
19
|
"dist/**/*.js",
|
|
12
20
|
"dist/**/*.d.ts",
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
"scripts": {
|
|
22
30
|
"bundle:skills": "bun scripts/bundle-skills.ts",
|
|
23
31
|
"export:admin-manifest": "bun scripts/export-admin-command-manifest.ts",
|
|
24
|
-
"build": "bun run bundle:skills && tsc --noEmit && bun build ./src/cli.ts ./src/prompt-source-mcp.ts --target=node --format=esm --outdir=dist --splitting --external @inquirer/prompts --external open --external zod",
|
|
32
|
+
"build": "bun run bundle:skills && tsc --noEmit && tsc -p tsconfig.prompt-hash.json && bun build ./src/cli.ts ./src/prompt-source-mcp.ts ./src/prompt-hash.ts --target=node --format=esm --outdir=dist --splitting --external @inquirer/prompts --external open --external zod",
|
|
25
33
|
"dev": "tsc --watch",
|
|
26
34
|
"typecheck": "tsc --noEmit",
|
|
27
35
|
"test": "bun run bundle:skills && vitest run",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "moda.skill_index.v1",
|
|
3
|
-
"bundled_at": "2026-10-
|
|
4
|
-
"cli_version": "1.
|
|
3
|
+
"bundled_at": "2026-10-12T08:12:12.808Z",
|
|
4
|
+
"cli_version": "1.55.0",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"id": "integration-cloudflare-think",
|
|
8
|
-
"version": "1.1.
|
|
8
|
+
"version": "1.1.1",
|
|
9
9
|
"summary": "Instrument Think on Workers with AI SDK 7, prompt versions, and acknowledged JSON export.",
|
|
10
10
|
"targets": {
|
|
11
11
|
"language": "node",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: integration-cloudflare-think
|
|
3
3
|
name: "Integrate Moda with Cloudflare Think"
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
category: integration
|
|
6
6
|
targets:
|
|
7
7
|
language: node
|
|
@@ -94,7 +94,10 @@ the correct definition was registered.
|
|
|
94
94
|
Keep memory and variable substitutions separate from reusable template
|
|
95
95
|
versions. Think adds instructions/tools: verify actual system instructions
|
|
96
96
|
on each model span as well as the registry definition.
|
|
97
|
-
Reference: https://docs.moda.dev/prompt-management/attribution
|
|
97
|
+
Reference: https://docs.moda.dev/prompt-management/attribution#cloudflare-think-and-ai-sdk-7
|
|
98
|
+
and, for `.moda/prompts.yml` `sources:` mappings, https://docs.moda.dev/prompt-management/workflow#map-prompts-defined-in-code.
|
|
99
|
+
Without the `prompt` option no trace carries a prompt version, so prompt fixes
|
|
100
|
+
land `UNROUTED` ("top share 0%").
|
|
98
101
|
|
|
99
102
|
## 5. Validate through the original application route
|
|
100
103
|
|
package/skills/moda-cli/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: moda-cli
|
|
3
|
-
version: 2.
|
|
3
|
+
version: 2.8.0
|
|
4
4
|
description: Query Moda's AI agent trace analytics and manage code-first prompt versions from the terminal — semantic/keyword/hybrid message search, overview KPIs, topic clusters, message context, user frustration detections, tool failures, custom signals, false-positive review of detections, and moda prompts status/sync/promote. Use when the user asks about moda, modaflows, trace or conversation analytics, prompt management, user frustrations, agent observability, tool failure debugging, custom signals, marking a detection as a false positive, wants to find traces or tool calls about a topic, or wants to investigate how their AI agent is performing.
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -506,6 +506,7 @@ moda search "customer asked for a refund" --mode=semantic
|
|
|
506
506
|
moda search "SEARCH_NO_RESULTS" --mode=keyword
|
|
507
507
|
moda search "checkout error" --time-range=7d --limit=10
|
|
508
508
|
moda search "stripe.charges.create failed" --user-id=<id>
|
|
509
|
+
moda search "Иван" --conversation-id=<id> --mode=keyword # inside one trace
|
|
509
510
|
```
|
|
510
511
|
|
|
511
512
|
Searches trace messages at message grain. Pass `--include-tool-io` to
|
|
@@ -514,9 +515,17 @@ output previews), so it finds where an agent *did* something, not just where
|
|
|
514
515
|
it talked about it — tool-IO search is opt-in, not the default.
|
|
515
516
|
|
|
516
517
|
Flags: `--mode=keyword|semantic|hybrid` (default `hybrid`), `--user-id`,
|
|
518
|
+
`--conversation-id` (search inside one trace; no trace fallback),
|
|
517
519
|
`--time-range` (`all|1h|3d|7d|24h|30d|90d`, default `all`), `--limit` (1–100,
|
|
518
520
|
default 20). The query is a positional arg (max 500 chars).
|
|
519
521
|
|
|
522
|
+
**Index freshness.** Keyword matches (and hybrid's keyword half) fall back to
|
|
523
|
+
the original messages when the search index returns too few literal matches,
|
|
524
|
+
so recent, Cyrillic and partial-word text is still found. `coverage` reports
|
|
525
|
+
which source served the keyword leg (`keyword_source`) and how far the index
|
|
526
|
+
trails ingestion (`index_freshness`); a warning says so when your
|
|
527
|
+
`--time-range` reaches past the newest indexed message.
|
|
528
|
+
|
|
520
529
|
**Fallback.** If message-level search finds nothing but whole traces match
|
|
521
530
|
(same as `moda traces --search`), those traces come back in `results` with
|
|
522
531
|
`match_grain: "trace"`, plus `fallback.source: "traces_search"` and a warning.
|
|
@@ -773,17 +782,63 @@ dropped and `truncation.data.full_output_path` holds every example. Agent output
|
|
|
773
782
|
Use these only when working on the code-first prompt registry.
|
|
774
783
|
|
|
775
784
|
```bash
|
|
776
|
-
moda prompts init # create .moda/prompts.yml
|
|
777
|
-
moda prompts status # read-only local status
|
|
785
|
+
moda prompts init # create .moda/prompts.yml (version 2)
|
|
786
|
+
moda prompts status # read-only local status + manifest warnings
|
|
778
787
|
moda prompts diff # only changed/new/deleted since last sync
|
|
788
|
+
moda prompts migrate --dry-run # preview a version 1 -> 2 manifest rewrite
|
|
789
|
+
moda prompts migrate # apply it (atomic; comments kept)
|
|
790
|
+
moda prompts sync --dry-run # preview discovery + what would upload
|
|
779
791
|
moda prompts sync # upload changed prompt versions
|
|
792
|
+
moda prompts sync --from-harness # register mappings from .moda/harness-report.json first
|
|
780
793
|
moda prompts sync --watch # sync when local prompt hashes change
|
|
781
794
|
moda prompts promote support.triage --label=prod --version=pver_abc123
|
|
782
795
|
```
|
|
783
796
|
|
|
784
|
-
`status` and `
|
|
785
|
-
`.moda/prompts.lock.json`. `promote` moves a remote `dev`,
|
|
786
|
-
`prod` label to an existing version.
|
|
797
|
+
`status`, `diff` and `migrate --dry-run` are read-only. `sync` uploads prompt
|
|
798
|
+
versions and writes `.moda/prompts.lock.json`. `promote` moves a remote `dev`,
|
|
799
|
+
`staging`, or `prod` label to an existing version.
|
|
800
|
+
|
|
801
|
+
`.moda/prompts.yml` (version 2) has two ways to point at prompts:
|
|
802
|
+
|
|
803
|
+
```yaml
|
|
804
|
+
version: 2
|
|
805
|
+
prompt_paths: # exact fast-glob patterns from the repo root
|
|
806
|
+
- "prompts/**/*.prompt.md"
|
|
807
|
+
- "!prompts/archive/**"
|
|
808
|
+
sources: # prompts defined in code or config, mapped in place
|
|
809
|
+
- key: support.system # stable registry key (also the lockfile key)
|
|
810
|
+
path: src/agent.ts
|
|
811
|
+
selector: { symbol: SYSTEM_PROMPT } # or { pointer: /support/prompt } for JSON/YAML
|
|
812
|
+
- key: support.build
|
|
813
|
+
path: src/prompt.py
|
|
814
|
+
selector: { symbol: build_prompt }
|
|
815
|
+
kind: builder # text | messages | template | builder
|
|
816
|
+
dependencies:
|
|
817
|
+
- path: config/policy.txt
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
- `prompts status` returns `zeroMatchPatterns` (globs that match nothing),
|
|
821
|
+
`legacyOnlyFiles` and `legacyManifest`. A non-empty `legacyOnlyFiles` means a
|
|
822
|
+
manifest from an older CLI: those files used to sync (old discovery matched
|
|
823
|
+
`*.prompt.*` anywhere) but match nothing now. Run `moda prompts migrate
|
|
824
|
+
--dry-run`, then `moda prompts migrate`; it adds them as explicit paths so
|
|
825
|
+
their keys and lockfile entries are unchanged (`--discover` also scans for
|
|
826
|
+
code-defined prompts, but lists unverified ones under `pendingSources`
|
|
827
|
+
instead of adding them; verify those with `moda harness analyze
|
|
828
|
+
--experimental --concern=prompts`, then `sync --from-harness`). `moda
|
|
829
|
+
doctor` reports the same condition on its `prompts_discovered` check.
|
|
830
|
+
- `sync --from-harness` registers what resolves and lists the rest in
|
|
831
|
+
`harnessImport.skipped` with reasons; it fails only when nothing resolves
|
|
832
|
+
(regenerate with `moda harness analyze --experimental --concern=prompts`).
|
|
833
|
+
- Lockfile `contentHash` is `computePromptContentHash` from
|
|
834
|
+
`@moda-ai/cli/prompt-hash` (`PROMPT_HASH_VERSION` 1). Don't reimplement it.
|
|
835
|
+
|
|
836
|
+
To attribute runtime calls, stamp the lock entry `{ key, promptId, versionId }`
|
|
837
|
+
on telemetry. With Cloudflare Think / AI SDK 7, pass
|
|
838
|
+
`prompt: lock.prompts['<key>']` to `createThinkTelemetry` (`moda-ai/think`) or
|
|
839
|
+
`createAI7Telemetry` (`moda-ai/ai7`). A `moda fix` that lands `UNROUTED` with
|
|
840
|
+
"top share 0%" means the evidence carries no prompt version: wire this up,
|
|
841
|
+
then check `moda prompts status`.
|
|
787
842
|
|
|
788
843
|
`prompts ab` and `prompts propose --gate` run paid replays. Before sending
|
|
789
844
|
anything they compute planned playouts (cases × seeds × 2 arms) and print it
|