@moda-ai/cli 1.34.0 → 1.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -183,8 +183,13 @@ Independent review can classify a source as an authored definition, builder,
183
183
  prompt transport, runtime context, non-prompt, or uncertain even when the
184
184
  resolver stored it as text, messages, template, or builder. Usage is tracked
185
185
  separately as active, conditional, display-only, or unknown, with an endpoint
186
- verification flag; unknown usage does not reject an otherwise verified authored
187
- definition or builder. Helpers that forward prompt configuration or carry
186
+ verification flag. Automatic code/configuration registration requires an authored
187
+ definition or builder with independently reviewed active or conditional model use
188
+ and a local consumer citation. Custom framework hooks can qualify without a
189
+ recognized SDK binding. Consumer relationships are semantic reviewer judgments,
190
+ not deterministic dataflow proofs; locally valid citations alone do not establish
191
+ model use. The separate `endpointVerified` flag reports SDK provenance. Display-only sources stay visible as related; sources
192
+ with unknown use remain pending. Helpers that forward prompt configuration or carry
188
193
  runtime context are retained as related discovery candidates and diagnostics,
189
194
  but are not synced as prompt sources. Builder provenance remains the source
190
195
  mapping and dependency evidence, so discovery does not claim complete
@@ -195,6 +200,18 @@ surrounding relationships. Its definition citations, source hash, and source
195
200
  stability are checked locally. Malformed review JSON gets one format-only repair
196
201
  attempt; a repair that changes an already stated role or decision stays pending.
197
202
 
203
+ Before uploading, sync checks statically resolvable JavaScript/TypeScript lookups
204
+ into the repository's `.moda/prompts.lock.json`. A missing key stops the upload
205
+ and reports its source location. Preserve a stable runtime key by assigning it
206
+ to the intended source mapping, or update the lookup to the selected mapping key.
207
+ Dynamic lookups and other languages are not exhaustively checked; attribution
208
+ coverage remains partial. A real sync with candidates but no eligible sources
209
+ exits nonzero after printing discovery diagnostics; dry runs remain previews.
210
+
211
+ Configured mappings remain authoritative. To clean up sources previously
212
+ registered in error, remove their entries from `.moda/prompts.yml` and sync with
213
+ `--allow-untrack`. This removes local tracking, not historical registry versions.
214
+
198
215
  You can also configure `.moda/prompts.yml` directly:
199
216
 
200
217
  ```yaml
@@ -183017,6 +183017,7 @@ function reviewEvidence(rootDir, source, evidence) {
183017
183017
  if (!evidence.length)
183018
183018
  return { valid: false, endpointVerified: false, reason: "Reviewer supplied no evidence" };
183019
183019
  try {
183020
+ const endpoints = [];
183020
183021
  for (const item of evidence) {
183021
183022
  let text;
183022
183023
  try {
@@ -183032,20 +183033,21 @@ function reviewEvidence(rootDir, source, evidence) {
183032
183033
  continue;
183033
183034
  return { valid: false, endpointVerified: false, reason: `Invalid definition evidence line ${item.path}:${item.line}` };
183034
183035
  }
183036
+ if (item.kind === "model_endpoint" && item.reason.trim())
183037
+ endpoints.push(item);
183035
183038
  }
183036
183039
  const definition = evidence.some((item) => item.kind === "definition" && item.path === source.path && isSelectedDefinitionCitation(rootDir, source, item.line));
183037
183040
  if (!definition) {
183038
183041
  return { valid: false, endpointVerified: false, reason: "Reviewer evidence lacks a citation inside the selected definition" };
183039
183042
  }
183040
- const endpoint = evidence.filter((item) => item.kind === "model_endpoint");
183041
- const endpointVerified = endpoint.some((item) => {
183043
+ const endpointVerified = endpoints.some((item) => {
183042
183044
  try {
183043
183045
  return proveSink(rootDir, { path: item.path, line: item.line, api: "" }) === "constructed";
183044
183046
  } catch {
183045
183047
  return false;
183046
183048
  }
183047
183049
  });
183048
- return { valid: true, endpointVerified };
183050
+ return { valid: true, endpointVerified, reviewedConsumerCited: endpoints.length > 0 };
183049
183051
  } catch (error) {
183050
183052
  return { valid: false, endpointVerified: false, reason: error instanceof Error ? error.message : String(error) };
183051
183053
  }
@@ -183065,7 +183067,8 @@ function promptVerificationInstruction(source, sourceHash, definition) {
183065
183067
  "Classify helpers by the value they carry, not the storage mechanism: persisting/retrieving/forwarding instruction settings, prompt definitions, or tool descriptors is prompt_transport; retrieving/formatting runtime facts, memory, documents, tool results, or ordinary user data is runtime_context. Neither operation alone authors or composes instructions.",
183066
183068
  "For a builder verdict, identify an actual instruction-composition operation owned by the selection: combining instruction fragments, assembling instructional messages, or rendering an instruction template with inputs. Merely passing an already-built value as a call argument, storing/retrieving it, or returning an identifier is not composition. A wrapper forwarding a prompt/tool descriptor remains transport or runtime_context unless it performs its own instruction composition. State the concrete operation in the definition evidence reason.",
183067
183069
  "A selected definition citation is required for every positive role. An endpoint witness is optional for role evidence and independently determines usage. UI labels/layout copy, retrieval output, ordinary configuration, and response adapters are non_prompt. Prompt presets, display-only instruction copies, tool/schema descriptions, and instruction spans remain authored when the selected definition owns that text. For mixed functions, authored instruction operations take precedence over transport/context operations; inspect the whole selected body.",
183068
- "usage.status is active only when you explicitly establish active model use and provide a trusted endpoint witness. Otherwise use unknown. conditional and display_only are semantic usage statuses, but endpointVerified must remain false unless deterministic proof exists.",
183070
+ "Instruction ownership alone does not qualify a source for automatic registration. Report usage.status active or conditional only after tracing this selected source to a concrete model input, including through imported builders, stored configuration, or a custom framework hook such as a returned system field. Cite that consumer as model_endpoint and explain the value flow from this source. A recognized SDK is not required for semantic usage; endpointVerified is computed separately by the local checker.",
183071
+ "Display copies, preset chips that only prefill an editor, and forms that concatenate or save editor state are display_only unless you trace the resulting value to an actual model consumer. Possible future use or the ability to click a UI control is not conditional model use. If the code is inconclusive, use unknown. Keep these definitions visible with their true semantic role; do not invent a model-use path to make them eligible.",
183069
183072
  "For lifecycle hooks that return a system/instructions field, cite the returned field as an endpoint witness only. If the selected hook only delegates to a separate builder, classify it as transport; if its own body authors instruction spans, authored role takes precedence.",
183070
183073
  `Candidate: ${JSON.stringify(source)}`,
183071
183074
  `Expected source hash: ${sourceHash}`,
@@ -183076,6 +183079,7 @@ function promptVerificationInstruction(source, sourceHash, definition) {
183076
183079
  ...definition?.declarationKind === "class" ? ["This selected definition is a class container. Do not inherit roles from separately addressable member methods: map the owning method instead. Only class-owned instruction initializers justify an authored role on the class itself; otherwise classify the container as non_prompt or uncertain and explain which members own the behavior."] : [],
183077
183080
  "Return exactly one JSON object between MODA_PROMPT_VERIFY_BEGIN and MODA_PROMPT_VERIFY_END:",
183078
183081
  '{"verified":true,"role":"authored_definition","usage":{"status":"unknown","reason":"No trusted endpoint established","endpointVerified":false},"sourceHash":"...","evidence":[{"path":"src/agent.ts","line":1,"kind":"definition","reason":"..."}],"reason":"..."}',
183082
+ "usage.status must be exactly one of: active, conditional, display_only, unknown. Use display_only (with an underscore) for editor-only or display-only instruction text. Do not emit inactive, none, or display-only. endpointVerified must be false in your response; only the local checker establishes that flag.",
183079
183083
  "Every response must include verified, role, sourceHash, evidence, and reason. role is authored_definition, builder, prompt_transport, runtime_context, non_prompt, or uncertain. Evidence kind is definition or model_endpoint and line is positive. Cite the endpoint expression START line when present."
183080
183084
  ].join(`
183081
183085
  `);
@@ -183170,7 +183174,7 @@ ${output}` : "";
183170
183174
  if (!reviewed.verified || reviewed.role === "uncertain")
183171
183175
  return pending(reviewed.reason, reviewed.evidence, reviewed.role, normalizeUsage(undefined, false));
183172
183176
  const evidence = reviewEvidence(rootDir, source, reviewed.evidence);
183173
- const usage = normalizeUsage(reviewed.usage, evidence.endpointVerified);
183177
+ const usage = normalizeUsage(reviewed.usage, evidence.endpointVerified, evidence.reviewedConsumerCited);
183174
183178
  if (!evidence.valid)
183175
183179
  return pending(evidence.reason ?? "Reviewer evidence is invalid", reviewed.evidence, reviewed.role, usage);
183176
183180
  return { status: "verified", reason: reviewed.reason, evidence: reviewed.evidence, sourceHash: checked.sourceHash, role: reviewed.role, usage };
@@ -183199,11 +183203,11 @@ function extractJudgment(output) {
183199
183203
  return;
183200
183204
  }
183201
183205
  }
183202
- function normalizeUsage(reviewed, endpointVerified) {
183206
+ function normalizeUsage(reviewed, endpointVerified, reviewedConsumerCited = false) {
183203
183207
  if (!reviewed)
183204
183208
  return { status: "unknown", reason: "Reviewer did not provide explicit usage status", endpointVerified };
183205
- if (reviewed.status === "active" && !endpointVerified)
183206
- return { status: "unknown", reason: "Active usage requires a deterministically trusted endpoint", endpointVerified: false };
183209
+ if ((reviewed.status === "active" || reviewed.status === "conditional") && !reviewedConsumerCited)
183210
+ return { status: "unknown", reason: "Model usage requires reviewer evidence at a valid model-input source location", endpointVerified: false };
183207
183211
  return { ...reviewed, endpointVerified };
183208
183212
  }
183209
183213
 
@@ -183445,9 +183449,18 @@ async function investigateHarnessPrompts(options) {
183445
183449
  candidate.role = decision.role;
183446
183450
  if (decision.usage)
183447
183451
  candidate.usage = decision.usage;
183448
- if (decision.status === "verified" && (decision.role === "authored_definition" || decision.role === "builder"))
183449
- candidate.status = "accepted";
183450
- else if (decision.status === "verified" && (decision.role === "prompt_transport" || decision.role === "runtime_context")) {
183452
+ if (decision.status === "verified" && decision.usage?.status === "display_only") {
183453
+ candidate.status = "related";
183454
+ candidate.reason = `Display-only source is visible in discovery but is not eligible for automatic registration. ${decision.reason}`;
183455
+ related.push(candidate);
183456
+ } else if (decision.status === "verified" && (decision.role === "authored_definition" || decision.role === "builder")) {
183457
+ if (decision.usage?.status === "active" || decision.usage?.status === "conditional")
183458
+ candidate.status = "accepted";
183459
+ else {
183460
+ candidate.status = "pending";
183461
+ candidate.reason = `Instruction ownership is verified, but model use is unresolved. ${decision.reason}`;
183462
+ }
183463
+ } else if (decision.status === "verified" && (decision.role === "prompt_transport" || decision.role === "runtime_context")) {
183451
183464
  candidate.status = "related";
183452
183465
  related.push(candidate);
183453
183466
  } else
@@ -184271,7 +184284,7 @@ async function runHarnessCommand(context) {
184271
184284
  throw new CliInputError("Focused remote prompt analysis is not supported yet.", "Use local --analyst=claude|codex|cursor|local-scan, or drop --concern for a full remote report.");
184272
184285
  }
184273
184286
  if (context.flags["github-actions"] === "true") {
184274
- const { runGithubActionsAnalyze } = await import("./harness-github-actions-43kt57xm.js");
184287
+ const { runGithubActionsAnalyze } = await import("./harness-github-actions-xk7n788g.js");
184275
184288
  const result = await runGithubActionsAnalyze(rootDir, {
184276
184289
  writeReport: (report2) => {
184277
184290
  const normalized = normalizeHarnessReport(report2);
@@ -188281,4 +188294,4 @@ function hashText(text) {
188281
188294
  return createHash4("sha256").update(text).digest("hex");
188282
188295
  }
188283
188296
 
188284
- export { ApiError, callDataAPI, callControlAPI, getApiRequestCount, resetApiRequestCount, $parseDocument, $stringify, inspectCodeSelectors, inspectPythonSelectors, resolvePromptSource, shouldColorTerminalOutput, terminalStyles, stripAnsi2 as stripAnsi, renderMarkdownForTerminal, HARNESS_REPORT_PATH, HARNESS_REPORT_APPROVAL_PATH, HARNESS_ANALYZE_CONCERNS, validateHarnessReport, readHarnessReport, hashHarnessReport, hashStableJson, startRemoteAnalyze, pollRemoteAnalyzeUntilDone, checkPromptMapping, safePromptEvidencePath, discoverHarnessPromptSources, renderDoctorHuman, renderStatusHuman, renderInitBanner, renderInitStep, renderInitNote, renderInitSetupPlan, renderInitComplete, runHarnessCommand, writeHarnessAnalyzeResult, emitWrittenArtifacts, analyzeHarnessPrompts, repairExternalAnalystReport, deriveRelationshipsFromGraph, mergeDeterministicArtifacts, describeCodingAgentEvent, analystEventProgress, isRetryableExternalAnalystFailure, sleep, summarizeInvalidAnalystReport, isDeterministicLocalAnalyst, resolveHarnessAnalyzeConcern, detectAvailableCodingAgents, codingAgentDisplayName, runCodingAgent, resolveAnalystModel, renderExternalHarnessAnalystPrompt, CONCERN_CANDIDATE_KINDS, SEED_CANDIDATE_KIND_LABELS, renderAnalystCandidateLines, renderAnalystSeedLines, truncateText, scanHarness, summarizeHarness, buildSourceSnapshot, writeHumanProgress, readHarnessSyncState, describeSyncIdentity, describeSyncTenantLabel, findFresherHarnessReports, writeHarnessArtifacts, assertHarnessGraphSyncQuality, graphForSync, inventoryFiles, numericEnv, numericEnvAllowZero, computeDeterministicFloor, stableId2 as stableId, titleFromId2 as titleFromId, promptNameFromPath };
188297
+ export { ApiError, callDataAPI, callControlAPI, getApiRequestCount, resetApiRequestCount, $parseDocument, $stringify, require_typescript, inspectCodeSelectors, inspectPythonSelectors, resolvePromptSource, shouldColorTerminalOutput, terminalStyles, stripAnsi2 as stripAnsi, renderMarkdownForTerminal, HARNESS_REPORT_PATH, HARNESS_REPORT_APPROVAL_PATH, HARNESS_ANALYZE_CONCERNS, validateHarnessReport, readHarnessReport, hashHarnessReport, hashStableJson, startRemoteAnalyze, pollRemoteAnalyzeUntilDone, checkPromptMapping, safePromptEvidencePath, discoverHarnessPromptSources, renderDoctorHuman, renderStatusHuman, renderInitBanner, renderInitStep, renderInitNote, renderInitSetupPlan, renderInitComplete, runHarnessCommand, writeHarnessAnalyzeResult, emitWrittenArtifacts, analyzeHarnessPrompts, repairExternalAnalystReport, deriveRelationshipsFromGraph, mergeDeterministicArtifacts, describeCodingAgentEvent, analystEventProgress, isRetryableExternalAnalystFailure, sleep, summarizeInvalidAnalystReport, isDeterministicLocalAnalyst, resolveHarnessAnalyzeConcern, detectAvailableCodingAgents, codingAgentDisplayName, runCodingAgent, resolveAnalystModel, renderExternalHarnessAnalystPrompt, CONCERN_CANDIDATE_KINDS, SEED_CANDIDATE_KIND_LABELS, renderAnalystCandidateLines, renderAnalystSeedLines, truncateText, scanHarness, summarizeHarness, buildSourceSnapshot, writeHumanProgress, readHarnessSyncState, describeSyncIdentity, describeSyncTenantLabel, findFresherHarnessReports, writeHarnessArtifacts, assertHarnessGraphSyncQuality, graphForSync, inventoryFiles, numericEnv, numericEnvAllowZero, computeDeterministicFloor, stableId2 as stableId, titleFromId2 as titleFromId, promptNameFromPath };
@@ -3,12 +3,15 @@ import {
3
3
  $stringify,
4
4
  callControlAPI,
5
5
  callDataAPI,
6
+ inventoryFiles,
6
7
  readHarnessSyncState,
7
8
  renderDoctorHuman,
8
9
  renderStatusHuman,
10
+ require_typescript,
9
11
  resolvePromptSource,
12
+ safePromptEvidencePath,
10
13
  scanHarness
11
- } from "./cli-xwqb83jb.js";
14
+ } from "./cli-8q7jffnj.js";
12
15
  import {
13
16
  isAuthSessionValid,
14
17
  loadAuthSession
@@ -5547,7 +5550,7 @@ import {
5547
5550
  rmSync,
5548
5551
  writeFileSync as writeFileSync2
5549
5552
  } from "node:fs";
5550
- import { dirname, isAbsolute as isAbsolute3, join, relative, resolve as resolve3 } from "node:path";
5553
+ import { dirname as dirname2, isAbsolute as isAbsolute3, join, relative, resolve as resolve4 } from "node:path";
5551
5554
 
5552
5555
  // src/prompts-ab.ts
5553
5556
  import { existsSync } from "node:fs";
@@ -6030,6 +6033,131 @@ function parsePositiveInt2(raw, fallback, max) {
6030
6033
  return Math.min(n, max);
6031
6034
  }
6032
6035
 
6036
+ // src/prompt-attribution.ts
6037
+ var import_typescript = __toESM(require_typescript(), 1);
6038
+ import { dirname, extname, resolve as resolve3 } from "node:path";
6039
+ function checkPromptAttribution(rootDir, keys) {
6040
+ const result = { references: [], coverage: "partial", issues: [] };
6041
+ const known = new Set(keys), lock = resolve3(rootDir, ".moda/prompts.lock.json");
6042
+ let files;
6043
+ try {
6044
+ files = inventoryFiles(rootDir, []);
6045
+ } catch (error) {
6046
+ result.issues.push({ path: ".", reason: `Attribution scan incomplete: ${error instanceof Error ? error.message : String(error)}` });
6047
+ return result;
6048
+ }
6049
+ for (const file of files) {
6050
+ if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(extname(file.path)) || !file.text?.includes("prompts.lock.json"))
6051
+ continue;
6052
+ if (/(^|\/)(?:tests?|__tests__|fixtures?)(\/|$)|\.(?:test|spec)\.[^.]+$/.test(file.path))
6053
+ continue;
6054
+ try {
6055
+ const absolute = safePromptEvidencePath(rootDir, file.path);
6056
+ const program = import_typescript.default.createProgram([absolute], { noResolve: true, noLib: true, types: [], allowJs: true });
6057
+ const sf = program.getSourceFile(absolute);
6058
+ if (!sf || program.getSyntacticDiagnostics(sf).length) {
6059
+ result.issues.push({ path: file.path, reason: "Attribution source could not be parsed" });
6060
+ continue;
6061
+ }
6062
+ const checker = program.getTypeChecker();
6063
+ const declaration = (node) => {
6064
+ const all = checker.getSymbolAtLocation(node)?.declarations;
6065
+ return all?.length === 1 ? all[0] : undefined;
6066
+ };
6067
+ const imported = (node) => {
6068
+ if (import_typescript.default.isImportSpecifier(node) && node.isTypeOnly)
6069
+ return false;
6070
+ let parent = node;
6071
+ while (parent && !import_typescript.default.isImportDeclaration(parent))
6072
+ parent = parent.parent;
6073
+ return !!parent && import_typescript.default.isImportDeclaration(parent) && !parent.importClause?.isTypeOnly && import_typescript.default.isStringLiteral(parent.moduleSpecifier) && parent.moduleSpecifier.text.startsWith(".") && resolve3(dirname(absolute), parent.moduleSpecifier.text) === lock;
6074
+ };
6075
+ const initializer = (node) => {
6076
+ const decl = declaration(node);
6077
+ return decl && import_typescript.default.isVariableDeclaration(decl) && import_typescript.default.isVariableDeclarationList(decl.parent) && !!(decl.parent.flags & import_typescript.default.NodeFlags.Const) ? decl.initializer : undefined;
6078
+ };
6079
+ const unwrap = (node) => {
6080
+ while (import_typescript.default.isParenthesizedExpression(node) || import_typescript.default.isAsExpression(node) || import_typescript.default.isTypeAssertionExpression(node) || import_typescript.default.isNonNullExpression(node))
6081
+ node = node.expression;
6082
+ return node;
6083
+ };
6084
+ const literal = (expression, seen = new Set) => {
6085
+ const node = unwrap(expression);
6086
+ if (seen.has(node))
6087
+ return;
6088
+ seen.add(node);
6089
+ if (import_typescript.default.isStringLiteral(node) || import_typescript.default.isNoSubstitutionTemplateLiteral(node))
6090
+ return node.text;
6091
+ if (import_typescript.default.isIdentifier(node)) {
6092
+ const init = initializer(node);
6093
+ return init ? literal(init, seen) : undefined;
6094
+ }
6095
+ if (import_typescript.default.isBinaryExpression(node) && node.operatorToken.kind === import_typescript.default.SyntaxKind.PlusToken) {
6096
+ const a = literal(node.left, new Set(seen)), b = literal(node.right, new Set(seen));
6097
+ return a !== undefined && b !== undefined ? a + b : undefined;
6098
+ }
6099
+ };
6100
+ const lockPath = (node) => import_typescript.default.isStringLiteral(node) && node.text.startsWith(".") && resolve3(dirname(absolute), node.text) === lock;
6101
+ const isConstBinding = (node) => import_typescript.default.isVariableDeclarationList(node.parent) && !!(node.parent.flags & import_typescript.default.NodeFlags.Const);
6102
+ const isLock = (expression, seen = new Set) => {
6103
+ const node = unwrap(expression);
6104
+ if (seen.has(node))
6105
+ return false;
6106
+ seen.add(node);
6107
+ if (import_typescript.default.isCallExpression(node) && import_typescript.default.isIdentifier(node.expression) && node.expression.text === "require" && !declaration(node.expression) && node.arguments.length === 1)
6108
+ return lockPath(node.arguments[0]);
6109
+ if (!import_typescript.default.isIdentifier(node))
6110
+ return false;
6111
+ const decl = declaration(node);
6112
+ if (decl && (import_typescript.default.isImportClause(decl) || import_typescript.default.isNamespaceImport(decl)))
6113
+ return imported(decl);
6114
+ const init = initializer(node);
6115
+ return !!init && isLock(init, seen);
6116
+ };
6117
+ const isTable = (expression, seen = new Set) => {
6118
+ const node = unwrap(expression);
6119
+ if (seen.has(node))
6120
+ return false;
6121
+ seen.add(node);
6122
+ if (import_typescript.default.isPropertyAccessExpression(node))
6123
+ return node.name.text === "prompts" && isLock(node.expression);
6124
+ if (import_typescript.default.isElementAccessExpression(node))
6125
+ return literal(node.argumentExpression) === "prompts" && isLock(node.expression);
6126
+ if (import_typescript.default.isIdentifier(node)) {
6127
+ const decl = declaration(node);
6128
+ if (decl && import_typescript.default.isBindingElement(decl) && import_typescript.default.isObjectBindingPattern(decl.parent) && import_typescript.default.isVariableDeclaration(decl.parent.parent)) {
6129
+ const owner = decl.parent.parent;
6130
+ const name = decl.propertyName ?? decl.name;
6131
+ if (!decl.dotDotDotToken && !decl.initializer && (import_typescript.default.isIdentifier(name) || import_typescript.default.isStringLiteral(name)) && name.text === "prompts" && isConstBinding(owner) && owner.initializer)
6132
+ return isLock(owner.initializer);
6133
+ }
6134
+ if (decl && import_typescript.default.isImportSpecifier(decl))
6135
+ return (decl.propertyName?.text ?? decl.name.text) === "prompts" && imported(decl);
6136
+ const init = initializer(node);
6137
+ return !!init && isTable(init, seen);
6138
+ }
6139
+ return false;
6140
+ };
6141
+ const visit = (node) => {
6142
+ if ((import_typescript.default.isElementAccessExpression(node) || import_typescript.default.isPropertyAccessExpression(node)) && isTable(node.expression)) {
6143
+ const key = import_typescript.default.isElementAccessExpression(node) ? literal(node.argumentExpression) : node.name.text;
6144
+ result.references.push({
6145
+ path: file.path,
6146
+ line: sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1,
6147
+ key,
6148
+ status: key === undefined ? "unresolved" : known.has(key) ? "mapped" : "missing"
6149
+ });
6150
+ }
6151
+ import_typescript.default.forEachChild(node, visit);
6152
+ };
6153
+ visit(sf);
6154
+ } catch (error) {
6155
+ result.issues.push({ path: file.path, reason: error instanceof Error ? error.message : String(error) });
6156
+ }
6157
+ }
6158
+ return result;
6159
+ }
6160
+
6033
6161
  // src/prompts.ts
6034
6162
  var MODA_DIR = ".moda";
6035
6163
  var MANIFEST_PATH = join(MODA_DIR, "prompts.yml");
@@ -6101,8 +6229,8 @@ async function runPromptsCommand(input, legacyFlags, legacyProfileOptions) {
6101
6229
  }
6102
6230
  async function initPrompts(options = {}) {
6103
6231
  const cwd = process.cwd();
6104
- const manifestPath = resolve3(cwd, MANIFEST_PATH);
6105
- const lockPath = resolve3(cwd, LOCK_PATH);
6232
+ const manifestPath = resolve4(cwd, MANIFEST_PATH);
6233
+ const lockPath = resolve4(cwd, LOCK_PATH);
6106
6234
  if (existsSync2(MODA_DIR) && lstatSync(MODA_DIR).isSymbolicLink())
6107
6235
  throw new Error(".moda must not be a symlink.");
6108
6236
  mkdirSync(MODA_DIR, { recursive: true });
@@ -6151,8 +6279,8 @@ async function syncPrompts(flags, profileOptions, context) {
6151
6279
  async function runPromptSync(flags, profileOptions, options = {}) {
6152
6280
  if (flags["dry-run"] === "true")
6153
6281
  return runPromptSyncExclusive(flags, profileOptions, options);
6154
- mkdirSync(dirname(MANIFEST_PATH), { recursive: true });
6155
- const syncLock = resolve3(dirname(MANIFEST_PATH), "prompts.sync.lock");
6282
+ mkdirSync(dirname2(MANIFEST_PATH), { recursive: true });
6283
+ const syncLock = resolve4(dirname2(MANIFEST_PATH), "prompts.sync.lock");
6156
6284
  try {
6157
6285
  mkdirSync(syncLock);
6158
6286
  } catch (error) {
@@ -6181,7 +6309,7 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
6181
6309
  const analyzedDefinitions = new Map;
6182
6310
  if (flags["no-analyze"] !== "true") {
6183
6311
  options.onProgress?.("Analyzing prompt sources before sync.");
6184
- const { analyzeHarnessPrompts } = await import("./harness-g0jbmgsx.js");
6312
+ const { analyzeHarnessPrompts } = await import("./harness-1xj4f0qp.js");
6185
6313
  const result = await analyzeHarnessPrompts({
6186
6314
  rootDir: process.cwd(),
6187
6315
  existing: established.flatMap((prompt) => prompt.sourceDefinition ? [prompt.sourceDefinition.source] : []),
@@ -6190,10 +6318,10 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
6190
6318
  dryRun: flags["dry-run"] === "true",
6191
6319
  onProgress: options.onProgress
6192
6320
  });
6193
- const legacyPaths = new Set(established.filter((prompt) => !prompt.sourceDefinition).map((prompt) => resolve3(prompt.sourcePath)));
6321
+ const legacyPaths = new Set(established.filter((prompt) => !prompt.sourceDefinition).map((prompt) => resolve4(prompt.sourcePath)));
6194
6322
  const exclusions = manifest.promptPaths.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
6195
- const excludedPaths = new Set(exclusions.length ? import_fast_glob.default.sync(exclusions, { cwd: process.cwd(), onlyFiles: true, dot: true, followSymbolicLinks: false }).map((path) => resolve3(path)) : []);
6196
- const found = result.sources.filter((source) => !legacyPaths.has(resolve3(source.path)) && !excludedPaths.has(resolve3(source.path)));
6323
+ const excludedPaths = new Set(exclusions.length ? import_fast_glob.default.sync(exclusions, { cwd: process.cwd(), onlyFiles: true, dot: true, followSymbolicLinks: false }).map((path) => resolve4(path)) : []);
6324
+ const found = result.sources.filter((source) => !legacyPaths.has(resolve4(source.path)) && !excludedPaths.has(resolve4(source.path)));
6197
6325
  for (const source of found) {
6198
6326
  const analyzed = result.resolved.find((item) => item.key === source.key);
6199
6327
  if (!analyzed)
@@ -6209,6 +6337,11 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
6209
6337
  const mergedSources = mergePromptSources(manifest.sources, extraSources);
6210
6338
  const manifestUpdate = stableJson(mergedSources) === stableJson(mergePromptSources(manifest.sources, [])) ? undefined : $stringify({ version: 2, prompt_paths: manifest.promptPaths, sources: mergedSources });
6211
6339
  const prompts = discoverPrompts(extraSources);
6340
+ const attribution = checkPromptAttribution(process.cwd(), prompts.map((prompt) => prompt.key));
6341
+ const brokenAttribution = attribution.references.filter((reference) => reference.status === "missing");
6342
+ if (brokenAttribution.length) {
6343
+ throw new Error(`Prompt attribution keys are absent from this sync: ${brokenAttribution.map((reference) => `'${reference.key}' at ${reference.path}:${reference.line}`).join(", ")}. Map the referenced key to its verified source in .moda/prompts.yml, or update the runtime lookup to the intended mapped key. No prompt versions were uploaded.`);
6344
+ }
6212
6345
  for (const prompt of prompts) {
6213
6346
  const analyzed = analyzedDefinitions.get(prompt.key);
6214
6347
  if (analyzed && analyzed !== stableJson(prompt.sourceDefinition))
@@ -6226,7 +6359,7 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
6226
6359
  if (prompts.length === 0) {
6227
6360
  if (!dryRun && flags["allow-untrack"] === "true")
6228
6361
  writeLock([], []);
6229
- return { synced: [], dryRun, discovery };
6362
+ return { synced: [], dryRun, discovery, attribution };
6230
6363
  }
6231
6364
  const signature = stableJson(prompts);
6232
6365
  const response = await callControlAPI("/prompts/sync", {
@@ -6258,7 +6391,7 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
6258
6391
  atomicWrite(MANIFEST_PATH, manifestUpdate);
6259
6392
  writeLock(prompts, synced);
6260
6393
  }
6261
- return { synced: response.synced ?? [], dryRun, discovery };
6394
+ return { synced: response.synced ?? [], dryRun, discovery, attribution };
6262
6395
  }
6263
6396
  function writePromptSyncProgress(message, context, quiet = false) {
6264
6397
  if (quiet || context?.quiet)
@@ -6277,12 +6410,17 @@ async function syncPromptsOnce(flags, profileOptions, context) {
6277
6410
  const payload = {
6278
6411
  synced: result.synced,
6279
6412
  discovery: result.discovery,
6413
+ attribution: result.attribution,
6280
6414
  lockfile: result.dryRun ? undefined : LOCK_PATH
6281
6415
  };
6282
6416
  if (context)
6283
6417
  context.output.writeData(payload);
6284
6418
  else
6285
6419
  printJson(payload);
6420
+ if (!result.dryRun && result.synced.length === 0 && result.discovery?.candidates?.length) {
6421
+ writePromptSyncProgress("No eligible prompt sources were synced. Review discovery.pending and discovery.related.", context, flags.quiet === "true");
6422
+ process.exitCode = 1;
6423
+ }
6286
6424
  }
6287
6425
  async function watchPromptSync(flags, profileOptions) {
6288
6426
  await syncPromptsOnce(flags, profileOptions);
@@ -6298,7 +6436,7 @@ async function watchPromptSync(flags, profileOptions) {
6298
6436
  await syncPromptsOnce({ ...flags, watch: "false", "no-analyze": "true" }, profileOptions);
6299
6437
  }
6300
6438
  }
6301
- await new Promise((resolve4) => setTimeout(resolve4, intervalMs));
6439
+ await new Promise((resolve5) => setTimeout(resolve5, intervalMs));
6302
6440
  }
6303
6441
  }
6304
6442
  async function promotePrompt(positionals, flags, profileOptions, context) {
@@ -6362,7 +6500,7 @@ function discoverPrompts(extraSources = []) {
6362
6500
  const positive = pattern.startsWith("!") ? pattern.slice(1) : pattern;
6363
6501
  if (isAbsolute3(positive) || positive.split(/[\\/]/).includes(".."))
6364
6502
  throw new Error(`Prompt pattern must stay inside the repository: ${pattern}`);
6365
- if (!pattern.startsWith("!") && !import_fast_glob.default.isDynamicPattern(pattern) && !existsSync2(resolve3(root, pattern))) {
6503
+ if (!pattern.startsWith("!") && !import_fast_glob.default.isDynamicPattern(pattern) && !existsSync2(resolve4(root, pattern))) {
6366
6504
  throw new Error(`Configured prompt file is missing: ${pattern}`);
6367
6505
  }
6368
6506
  }
@@ -6377,11 +6515,11 @@ function discoverPrompts(extraSources = []) {
6377
6515
  const sources = new Map(manifest.sources.map((source) => [source.key, source]));
6378
6516
  for (const source of extraSources)
6379
6517
  sources.set(source.key, source);
6380
- const selectedPaths = new Set([...sources.values()].map((source) => resolve3(root, source.path)));
6381
- const prompts = paths.filter((path) => !selectedPaths.has(resolve3(root, path))).map((path) => {
6518
+ const selectedPaths = new Set([...sources.values()].map((source) => resolve4(root, source.path)));
6519
+ const prompts = paths.filter((path) => !selectedPaths.has(resolve4(root, path))).map((path) => {
6382
6520
  assertContainedSource(root, path);
6383
6521
  if (/\.prompt\.(md|json)$/i.test(path))
6384
- return parsePromptFile(resolve3(root, path));
6522
+ return parsePromptFile(resolve4(root, path));
6385
6523
  return normalizePrompt(resolvePromptSource(root, { key: keyFromPath(path), path }));
6386
6524
  });
6387
6525
  for (const source of sources.values())
@@ -6395,15 +6533,15 @@ function discoverPrompts(extraSources = []) {
6395
6533
  return prompts.sort((a, b) => a.key.localeCompare(b.key));
6396
6534
  }
6397
6535
  function assertContainedSource(root, path) {
6398
- const abs = resolve3(root, path);
6536
+ const abs = resolve4(root, path);
6399
6537
  const rel = relative(realpathSync(root), realpathSync(abs));
6400
6538
  if (rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute3(rel))
6401
6539
  throw new Error(`Prompt source escapes repository: ${path}`);
6402
6540
  let cursor = abs;
6403
- while (cursor !== resolve3(root)) {
6541
+ while (cursor !== resolve4(root)) {
6404
6542
  if (lstatSync(cursor).isSymbolicLink())
6405
6543
  throw new Error(`Prompt source uses a symlink: ${path}`);
6406
- const next = dirname(cursor);
6544
+ const next = dirname2(cursor);
6407
6545
  if (next === cursor)
6408
6546
  throw new Error(`Prompt source escapes repository: ${path}`);
6409
6547
  cursor = next;
@@ -6520,7 +6658,7 @@ function readManifest() {
6520
6658
  function readHarnessSources(reportPath) {
6521
6659
  const selected = reportPath === "true" ? ".moda/harness-report.json" : reportPath;
6522
6660
  assertContainedSource(process.cwd(), selected);
6523
- const report = JSON.parse(readFileSync(resolve3(selected), "utf8"));
6661
+ const report = JSON.parse(readFileSync(resolve4(selected), "utf8"));
6524
6662
  if (!Array.isArray(report?.graph?.artifacts))
6525
6663
  throw new Error("Expected a harness report with graph.artifacts.");
6526
6664
  const artifacts = report.graph.artifacts.filter((artifact) => artifact.type === "prompt");
@@ -6555,8 +6693,8 @@ function mergePromptSources(existingSources, sources) {
6555
6693
  return [...merged.values()].sort((a, b) => a.key.localeCompare(b.key));
6556
6694
  }
6557
6695
  function atomicWrite(path, content) {
6558
- mkdirSync(dirname(path), { recursive: true });
6559
- assertContainedSource(process.cwd(), dirname(path));
6696
+ mkdirSync(dirname2(path), { recursive: true });
6697
+ assertContainedSource(process.cwd(), dirname2(path));
6560
6698
  if (existsSync2(path) && lstatSync(path).isSymbolicLink())
6561
6699
  throw new Error(`Refusing to overwrite symlink: ${path}`);
6562
6700
  const temp = `${path}.${process.pid}.tmp`;
@@ -6597,7 +6735,7 @@ function writeLock(prompts, synced) {
6597
6735
  }];
6598
6736
  }))
6599
6737
  };
6600
- mkdirSync(dirname(LOCK_PATH), { recursive: true });
6738
+ mkdirSync(dirname2(LOCK_PATH), { recursive: true });
6601
6739
  atomicWrite(LOCK_PATH, `${JSON.stringify(lock, null, 2)}
6602
6740
  `);
6603
6741
  }
@@ -6658,10 +6796,10 @@ import { join as join3 } from "node:path";
6658
6796
  import { confirm } from "@inquirer/prompts";
6659
6797
 
6660
6798
  // src/package-assets.ts
6661
- import { dirname as dirname2, join as join2 } from "node:path";
6799
+ import { dirname as dirname3, join as join2 } from "node:path";
6662
6800
  import { fileURLToPath } from "node:url";
6663
6801
  function packageAssetPath(...parts) {
6664
- return join2(dirname2(fileURLToPath(import.meta.url)), "..", ...parts);
6802
+ return join2(dirname3(fileURLToPath(import.meta.url)), "..", ...parts);
6665
6803
  }
6666
6804
 
6667
6805
  // src/init/rules.ts
@@ -6838,7 +6976,7 @@ import {
6838
6976
  readFileSync as readFileSync5,
6839
6977
  writeFileSync as writeFileSync6
6840
6978
  } from "node:fs";
6841
- import { basename, dirname as dirname5, join as join7, relative as relative3 } from "node:path";
6979
+ import { basename, dirname as dirname6, join as join7, relative as relative3 } from "node:path";
6842
6980
 
6843
6981
  // src/skills-manifest.ts
6844
6982
  import {
@@ -6850,7 +6988,7 @@ import {
6850
6988
  unlinkSync,
6851
6989
  writeFileSync as writeFileSync4
6852
6990
  } from "node:fs";
6853
- import { dirname as dirname3, isAbsolute as isAbsolute4, join as join5, normalize, relative as relative2, resolve as resolve4 } from "node:path";
6991
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join5, normalize, relative as relative2, resolve as resolve5 } from "node:path";
6854
6992
  var SKILLS_MANIFEST_PATH = join5(".moda", "skills.yml");
6855
6993
  var SKILL_SOURCE_HARNESS = "moda";
6856
6994
  var SKILL_SOURCE_INTEGRATION_BUNDLED = "integration-bundled";
@@ -6910,7 +7048,7 @@ function writeSkillsManifest(cwd, header, records) {
6910
7048
  ]),
6911
7049
  ""
6912
7050
  ];
6913
- mkdirSync3(dirname3(manifestPath), { recursive: true });
7051
+ mkdirSync3(dirname4(manifestPath), { recursive: true });
6914
7052
  writeFileSync4(manifestPath, lines.join(`
6915
7053
  `));
6916
7054
  }
@@ -6941,7 +7079,7 @@ function pruneManagedSkillPaths(cwd, managedPaths, log) {
6941
7079
  function pruneEmptyClaudeSkillDir(cwd, relativePath) {
6942
7080
  if (!relativePath.startsWith(".claude/skills/") || !relativePath.endsWith("/SKILL.md"))
6943
7081
  return;
6944
- const dir = dirname3(join5(cwd, relativePath));
7082
+ const dir = dirname4(join5(cwd, relativePath));
6945
7083
  try {
6946
7084
  if (existsSync5(dir) && readdirSync(dir).length === 0) {
6947
7085
  rmdirSync(dir);
@@ -6949,8 +7087,8 @@ function pruneEmptyClaudeSkillDir(cwd, relativePath) {
6949
7087
  } catch {}
6950
7088
  }
6951
7089
  function absoluteManagedPath(cwd, relativePath) {
6952
- const abs = resolve4(cwd, relativePath);
6953
- const root = resolve4(cwd);
7090
+ const abs = resolve5(cwd, relativePath);
7091
+ const root = resolve5(cwd);
6954
7092
  const rel = relative2(root, abs);
6955
7093
  if (rel.startsWith("..") || isAbsolute4(rel))
6956
7094
  return null;
@@ -7061,7 +7199,7 @@ function yamlQuote(value) {
7061
7199
 
7062
7200
  // src/agents-md.ts
7063
7201
  import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
7064
- import { dirname as dirname4, join as join6 } from "node:path";
7202
+ import { dirname as dirname5, join as join6 } from "node:path";
7065
7203
  var AGENTS_MD_FILENAME = "AGENTS.md";
7066
7204
  var FIXES_BLOCK_BEGIN = "<!-- moda:fixes:begin -->";
7067
7205
  var FIXES_BLOCK_END = "<!-- moda:fixes:end -->";
@@ -7107,7 +7245,7 @@ function upsertManagedBlock(existing, block) {
7107
7245
  }
7108
7246
  function writeFileIfChanged(filePath, content) {
7109
7247
  try {
7110
- mkdirSync4(dirname4(filePath), { recursive: true });
7248
+ mkdirSync4(dirname5(filePath), { recursive: true });
7111
7249
  if (existsSync6(filePath)) {
7112
7250
  const current = readFileSync4(filePath, "utf8");
7113
7251
  if (current === content)
@@ -7263,7 +7401,7 @@ function formatRunStatusHuman(run, events) {
7263
7401
  }
7264
7402
  function writeIfChanged(filePath, content) {
7265
7403
  try {
7266
- mkdirSync5(dirname5(filePath), { recursive: true });
7404
+ mkdirSync5(dirname6(filePath), { recursive: true });
7267
7405
  if (existsSync7(filePath)) {
7268
7406
  const current = readFileSync5(filePath, "utf8");
7269
7407
  if (current === content)
@@ -7407,7 +7545,7 @@ function discoverLocalSkills(cwd) {
7407
7545
  const sourcePath = relative3(cwd, absPath).replace(/\\/g, "/");
7408
7546
  if (managed.has(sourcePath))
7409
7547
  continue;
7410
- const key = basename(dirname5(absPath));
7548
+ const key = basename(dirname6(absPath));
7411
7549
  if (key === "moda-cli" || !/^[A-Za-z0-9_-]+$/.test(key))
7412
7550
  continue;
7413
7551
  let content;
@@ -7555,7 +7693,7 @@ function safeSkillId(skillId) {
7555
7693
  }
7556
7694
  function writeSkillProposalManifest(cwd, proposal, relativePath) {
7557
7695
  const manifestPath = join7(cwd, SKILL_PROPOSALS_MANIFEST_PATH);
7558
- mkdirSync5(dirname5(manifestPath), { recursive: true });
7696
+ mkdirSync5(dirname6(manifestPath), { recursive: true });
7559
7697
  const existing = existsSync7(manifestPath) ? readFileSync5(manifestPath, "utf8") : `applied_proposals:
7560
7698
  `;
7561
7699
  const entry = [
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  runPromptsCommand,
9
9
  runSkillsCommand,
10
10
  runStatusCommand
11
- } from "./cli-q9t1tr1z.js";
11
+ } from "./cli-cagbc7wz.js";
12
12
  import {
13
13
  ApiError,
14
14
  HARNESS_REPORT_APPROVAL_PATH,
@@ -29,7 +29,7 @@ import {
29
29
  summarizeHarness,
30
30
  terminalStyles,
31
31
  validateHarnessReport
32
- } from "./cli-xwqb83jb.js";
32
+ } from "./cli-8q7jffnj.js";
33
33
  import {
34
34
  authFetch,
35
35
  clearAuthSession,
@@ -6580,7 +6580,7 @@ var commandRegistry = createCommandRegistry([
6580
6580
  resetApiRequestCountBeforeRun: true,
6581
6581
  telemetry: "result",
6582
6582
  handler: async (context) => {
6583
- const { runInit } = await import("./index-a0hvade2.js");
6583
+ const { runInit } = await import("./index-h6gg66n5.js");
6584
6584
  if (context.outputMode === "agent-stream") {
6585
6585
  context.output.writeEvent({
6586
6586
  event: "started",
@@ -42,7 +42,7 @@ import {
42
42
  writeHarnessAnalyzeResult,
43
43
  writeHarnessArtifacts,
44
44
  writeHumanProgress
45
- } from "./cli-xwqb83jb.js";
45
+ } from "./cli-8q7jffnj.js";
46
46
  import"./cli-0h32yvwq.js";
47
47
  export {
48
48
  writeHumanProgress,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildSourceSnapshot
3
- } from "./cli-xwqb83jb.js";
3
+ } from "./cli-8q7jffnj.js";
4
4
  import"./cli-0h32yvwq.js";
5
5
 
6
6
  // src/harness-github-actions.ts
@@ -10,7 +10,7 @@ import {
10
10
  runPromptSync,
11
11
  runSkillSync,
12
12
  upsertSkillManifestRecord
13
- } from "./cli-q9t1tr1z.js";
13
+ } from "./cli-cagbc7wz.js";
14
14
  import {
15
15
  codingAgentDisplayName,
16
16
  describeCodingAgentEvent,
@@ -28,7 +28,7 @@ import {
28
28
  runHarnessCommand,
29
29
  startRemoteAnalyze,
30
30
  stripAnsi
31
- } from "./cli-xwqb83jb.js";
31
+ } from "./cli-8q7jffnj.js";
32
32
  import {
33
33
  selectTenantAndCreateKey
34
34
  } from "./cli-4ycvzfdg.js";
@@ -5,7 +5,7 @@ import {
5
5
  inspectCodeSelectors,
6
6
  inspectPythonSelectors,
7
7
  safePromptEvidencePath
8
- } from "./cli-xwqb83jb.js";
8
+ } from "./cli-8q7jffnj.js";
9
9
  import {
10
10
  __commonJS,
11
11
  __toESM
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.34.0",
3
+ "version": "1.34.1",
4
4
  "description": "CLI for Moda - AI agent analytics and observability",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": "moda.skill_index.v1",
3
- "bundled_at": "2026-09-21T22:32:21.368Z",
4
- "cli_version": "1.34.0",
3
+ "bundled_at": "2026-09-22T23:28:01.590Z",
4
+ "cli_version": "1.34.1",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-cloudflare-think",