@minhpnq1807/contextos 0.5.45 → 0.5.46

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.46
4
+
5
+ - **Configurable prompt suggestion limits:** `ctx --config` and interactive `ctx setup` now let users choose how many suggested files, skills, and workflows appear in prompt context. Defaults are five each, with caps of 20 files, 10 skills, and 5 workflows.
6
+ - **Limit-aware prompt hooks and debug:** `UserPromptSubmit` hooks, direct fallback scoring, the private `ctx-mcp` bridge request, and `ctx debug` now all honor the saved suggestion limits instead of using hard-coded counts.
7
+ - **Document authoring skill intent:** Prompts that create, edit, update, or maintain documents, workspace docs, README files, wiki pages, manuals, guides, specs, or ADRs now prioritize documentation skills such as `doc-coauthoring`, `documentation`, `docs-architect`, `readme`, and `wiki-page-writer`.
8
+ - **Safer document skill gating:** Document-processing and workspace-automation skills such as Azure Document Intelligence, DocuSign, Asana, Slack, Google Docs, and Notion no longer win generic document-writing prompts unless the provider or processing task is explicitly named.
9
+ - **Setup summary clarity:** The setup wizard summary now reports the saved prompt suggestion limits alongside the enabled prompt sections so users can review output volume immediately.
10
+
3
11
  ## 0.5.45
4
12
 
5
13
  - **Project-aware MCP skill suggestions:** Skill ranking now reads `package.json` keywords and dependencies such as `@modelcontextprotocol/sdk`. MCP projects can recommend `mcp-builder`, `mcp-management`, `mcp-tool-developer`, and `agent-memory-mcp` for context retrieval, scorer, hook, and prompt-injection debugging tasks even when the prompt does not explicitly say `mcp`.
package/README.md CHANGED
@@ -444,7 +444,7 @@ This warning comes from a transitive dependency in the local embedding/WASM stac
444
444
  | `ctx sync --workflows --dry-run` | Previews workflow sync without writing files. | You want to inspect source workflows and target roots first. | Prints planned sync/index output and skips copying target files. |
445
445
  | `ctx skills` | Installs community skill libraries. | You want curated skills without running the full setup wizard. | Opens the community installer, uses a portable shell on Windows/Linux/macOS, repairs unsafe skill symlinks, and syncs installed skills to selected agents. |
446
446
  | `ctx embeddings warm -- "task"` | Prepares local semantic embedding caches. | First install, CI smoke checks, or after changing AGENTS.md/project files/skills/workflows. | Loads/downloads `Xenova/all-MiniLM-L6-v2` and writes rule, file-path, skill, and workflow vectors to `~/.ctx/contextos/embeddings.db`. |
447
- | `ctx --config` | Opens an interactive multi-select panel for prompt sections. | You want to reduce ContextOS prompt output noise. | Toggles critical rules, suggested files, suggested skills, and suggested workflows globally under `~/.ctx/contextos/output-config.json`. |
447
+ | `ctx --config` | Opens an interactive panel for prompt sections and suggestion limits. | You want to reduce ContextOS prompt output noise. | Toggles critical rules, suggested files, suggested skills, and suggested workflows globally under `~/.ctx/contextos/output-config.json`, then lets you set suggestion counts for files, skills, and workflows. |
448
448
  | `ctx refresh` | Refreshes the active Codex marketplace plugin and rebuilds local indexes. | Local development updates or a stale file retrieval index. | Copies the current package to `$CODEX_HOME/marketplaces/contextos`, rebuilds file-path embeddings and import adjacency, and refreshes code-review-graph embeddings when available. |
449
449
  | `ctx ruler -- <args>` | Forwards args to the installed `ruler` CLI. | You need native Ruler commands such as `init`, `apply`, or `revert`. | Preserves Ruler stdout/stderr and exit status. |
450
450
  | `ctx skillshare -- <args>` | Forwards args to the installed `skillshare` CLI. | You need native skillshare commands such as `status`, `target list`, `doctor`, `push`, or `pull`. | Preserves skillshare stdout/stderr and exit status. |
@@ -515,13 +515,13 @@ Prompt scoring does not walk the repository for file candidates or import expans
515
515
 
516
516
  If a prompt has no usable context candidates, the hook fails open without emitting an empty `hook context` block, records `emptyContextReason` in the workspace runtime file, and starts a detached `autowarm` rebuild with a cooldown. That background rebuild refreshes file vectors, skill/workflow vectors, import adjacency, and available code-review-graph node embeddings for the next prompt while keeping repository walking out of the current prompt hot path.
517
517
 
518
- Use `ctx --config` to choose which prompt sections ContextOS injects. Interactive `ctx setup` now includes the same multi-select step, while `ctx setup --yes` keeps the current saved config for automation. The panel supports multiple selection with `Space` and persists the global choice in `~/.ctx/contextos/output-config.json`. Disabling rules hides both critical and additional relevant rule sections; compliance metadata remains available for reports.
518
+ Use `ctx --config` to choose which prompt sections ContextOS injects and how many suggestions each section may show. Interactive `ctx setup` includes the same section picker and limit prompts, while `ctx setup --yes` keeps the current saved config for automation. The panel supports multiple selection with `Space` and persists the global choice in `~/.ctx/contextos/output-config.json`. Defaults are five suggested files, five skills, and five workflows; caps are 20 files, 10 skills, and 5 workflows. Disabling rules hides both critical and additional relevant rule sections; compliance metadata remains available for reports.
519
519
 
520
- Injected prompt sections are intentionally compact: rules show only detected rule text, files show basenames without paths, skills show unique names as a comma-separated inline list without descriptions, and workflows show names with their agent chain. Stop hooks persist reports silently; run `ctx report` or `ctx evidence` when you want the detailed compliance output.
520
+ Injected prompt sections are intentionally compact: rules show only detected rule text, files show a comma-separated inline list of basenames without paths, skills show unique names as a comma-separated inline list without descriptions, and workflows show names with their agent chain. Stop hooks persist reports silently; run `ctx report` or `ctx evidence` when you want the detailed compliance output.
521
521
 
522
522
  Codex may flatten newlines in its `UserPromptSubmit hook (completed)` preview. The injected `additionalContext` payload remains multiline; this is a Codex preview display limitation.
523
523
 
524
- Skill ranking uses bounded project hints from root/workspace `package.json` files and known mobile config files such as `app.json`, `app.config.*`, and `eas.json`. This lets Expo/EAS tasks activate specialized skills without walking the source tree on every prompt.
524
+ Skill ranking uses bounded project hints from root/workspace `package.json` files and known mobile config files such as `app.json`, `app.config.*`, and `eas.json`. This lets Expo/EAS and MCP tasks activate specialized skills without walking the source tree on every prompt. Document-authoring prompts also get explicit intent handling for README, wiki, workspace documentation, guides, specs, and ADR work, while document-processing or workspace-automation providers only rank highly when the prompt actually names that provider or processing task.
525
525
 
526
526
  After `ctx refresh`, ContextOS invalidates the private hook bridge socket so prompts fall back to direct scoring until Codex restarts the long-running `ctx-mcp` process. Hook clients also discard a same-inode socket if an older bridge revision is detected.
527
527
 
package/bin/ctx.js CHANGED
@@ -34,7 +34,7 @@ import { scanSkills, warmSkillEmbeddings } from "../plugins/ctx/lib/skill-discov
34
34
  import { parsePassthroughArgs, runPassthrough } from "../plugins/ctx/lib/passthrough.js";
35
35
  import { parseAgentList, parseSetupArgs, setupSummaryLines } from "../plugins/ctx/lib/setup-wizard.js";
36
36
  import { multiSelect } from "../plugins/ctx/lib/multi-select.js";
37
- import { configureOutputSections, enabledOutputSectionsLabel, loadOutputConfig } from "../plugins/ctx/lib/output-config.js";
37
+ import { configureOutputSections, enabledOutputSectionsLabel, loadOutputConfig, outputConfigLimits, outputConfigLimitsLabel } from "../plugins/ctx/lib/output-config.js";
38
38
  import { syncWorkflows, warmWorkflowEmbeddings } from "../plugins/ctx/lib/workflow-discoverer.js";
39
39
  import { checkForUpdate } from "../plugins/ctx/lib/update-notifier.js";
40
40
  import { fetchSkillsForAgents, printSkillRecommendations, getAllLibraries, getInstallCommands } from "../plugins/ctx/lib/skill-library.js";
@@ -586,18 +586,20 @@ function contextOSWorkspaceDataDir(cwd = process.cwd()) {
586
586
 
587
587
  async function debug(task) {
588
588
  const cwd = process.cwd();
589
+ const limits = outputConfigLimits(loadOutputConfig({ dataRoot: contextOSDataDir() }));
589
590
  const scored = await scoreContext({
590
591
  cwd,
591
592
  prompt: task,
592
593
  dataDir: contextOSDataDir(),
593
- maxFiles: 7,
594
- maxSkills: 7,
594
+ maxFiles: limits.files,
595
+ maxSkills: limits.skills,
596
+ maxWorkflows: limits.workflows,
595
597
  embeddingTimeoutMs: Number(process.env.CONTEXTOS_EMBEDDING_DEBUG_TIMEOUT_MS || 5000)
596
598
  });
597
599
  const rules = scored.scoredRules;
598
- const relevantFiles = scored.suggestedFiles.slice(0, 7);
599
- const suggestedSkills = (scored.suggestedSkills || []).slice(0, 7);
600
- const suggestedWorkflows = (scored.suggestedWorkflows || []).slice(0, 2);
600
+ const relevantFiles = scored.suggestedFiles.slice(0, limits.files);
601
+ const suggestedSkills = (scored.suggestedSkills || []).slice(0, limits.skills);
602
+ const suggestedWorkflows = (scored.suggestedWorkflows || []).slice(0, limits.workflows);
601
603
  const scheduled = scheduleContext({ rules, relevantFiles, suggestedSkills, suggestedWorkflows });
602
604
 
603
605
  console.log("ContextOS debug");
@@ -728,6 +730,21 @@ async function askSetupYesNo(rl, question, defaultValue = true) {
728
730
  return !/^n(o)?$/i.test(answer.trim());
729
731
  }
730
732
 
733
+ async function askOutputLimit({ option, currentValue }) {
734
+ if (!process.stdin.isTTY) return currentValue;
735
+ const rl = readline.createInterface({ input, output });
736
+ try {
737
+ const answer = await rl.question(`◇ ${option.label} limit (0-${option.max}, current ${currentValue}): `);
738
+ const trimmed = answer.trim();
739
+ if (!trimmed) return currentValue;
740
+ const value = Number(trimmed);
741
+ if (!Number.isFinite(value)) return currentValue;
742
+ return Math.max(0, Math.min(option.max, Math.trunc(value)));
743
+ } finally {
744
+ rl.close();
745
+ }
746
+ }
747
+
731
748
  async function setup({ args = [], cwd = process.cwd() } = {}) {
732
749
  const options = parseSetupArgs(args);
733
750
  const interactive = !options.yes && process.stdin.isTTY;
@@ -777,7 +794,8 @@ async function setup({ args = [], cwd = process.cwd() } = {}) {
777
794
  console.log("◇ Configure prompt output:");
778
795
  outputConfig = await configureOutputSections({
779
796
  dataRoot: contextOSDataDir(),
780
- select: multiSelect
797
+ select: multiSelect,
798
+ askLimit: askOutputLimit
781
799
  });
782
800
  }
783
801
 
@@ -786,7 +804,8 @@ async function setup({ args = [], cwd = process.cwd() } = {}) {
786
804
  for (const line of setupSummaryLines({
787
805
  cwd,
788
806
  ...options,
789
- promptSections: enabledOutputSectionsLabel(outputConfig)
807
+ promptSections: enabledOutputSectionsLabel(outputConfig),
808
+ promptLimits: outputConfigLimitsLabel(outputConfig)
790
809
  })) console.log(`│ ${line}`);
791
810
  console.log("");
792
811
 
@@ -871,7 +890,8 @@ try {
871
890
  } else if (command === "--config" || command === "config") {
872
891
  await configureOutputSections({
873
892
  dataRoot: contextOSDataDir(),
874
- select: multiSelect
893
+ select: multiSelect,
894
+ askLimit: askOutputLimit
875
895
  });
876
896
  } else if (command === "install") {
877
897
  const copy = args.includes("--copy");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhpnq1807/contextos",
3
- "version": "0.5.45",
3
+ "version": "0.5.46",
4
4
  "description": "Task-aware AGENTS.md context injection and compliance reporting for Codex, Claude Code, and Antigravity.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctx",
3
- "version": "0.5.45",
3
+ "version": "0.5.46",
4
4
  "description": "Inject task-relevant AGENTS.md rules into Codex through plugin hooks.",
5
5
  "author": {
6
6
  "name": "ContextOS"
@@ -13,9 +13,16 @@ export const OUTPUT_SECTION_OPTIONS = [
13
13
  { value: "workflows", label: "Suggested workflow for this task", hint: "Include matching workflow recommendations." }
14
14
  ];
15
15
 
16
+ export const OUTPUT_LIMIT_OPTIONS = [
17
+ { value: "files", label: "Suggested files", defaultValue: 5, max: 20 },
18
+ { value: "skills", label: "Suggested skills", defaultValue: 5, max: 10 },
19
+ { value: "workflows", label: "Suggested workflows", defaultValue: 5, max: 5 }
20
+ ];
21
+
16
22
  export function defaultOutputConfig() {
17
23
  return {
18
- sections: Object.fromEntries(OUTPUT_SECTION_OPTIONS.map((option) => [option.value, true]))
24
+ sections: Object.fromEntries(OUTPUT_SECTION_OPTIONS.map((option) => [option.value, true])),
25
+ limits: Object.fromEntries(OUTPUT_LIMIT_OPTIONS.map((option) => [option.value, option.defaultValue]))
19
26
  };
20
27
  }
21
28
 
@@ -49,9 +56,19 @@ export function enabledOutputSectionsLabel(config = loadOutputConfig()) {
49
56
  return enabled.length ? enabled.join(", ") : "(none)";
50
57
  }
51
58
 
59
+ export function outputConfigLimits(config = loadOutputConfig()) {
60
+ return normalizeOutputConfig(config).limits;
61
+ }
62
+
63
+ export function outputConfigLimitsLabel(config = loadOutputConfig()) {
64
+ const limits = outputConfigLimits(config);
65
+ return OUTPUT_LIMIT_OPTIONS.map((option) => `${option.value}: ${limits[option.value]}`).join(", ");
66
+ }
67
+
52
68
  export async function configureOutputSections({
53
69
  dataRoot = defaultDataRoot(),
54
70
  select,
71
+ askLimit,
55
72
  logger = console.log
56
73
  } = {}) {
57
74
  if (typeof select !== "function") throw new Error("configureOutputSections requires a multi-select function");
@@ -64,11 +81,19 @@ export async function configureOutputSections({
64
81
  }))
65
82
  });
66
83
  const selectedSet = new Set(selected);
84
+ const limits = {};
85
+ for (const option of OUTPUT_LIMIT_OPTIONS) {
86
+ limits[option.value] = typeof askLimit === "function"
87
+ ? await askLimit({ option, currentValue: current.limits[option.value] })
88
+ : current.limits[option.value];
89
+ }
67
90
  const saved = saveOutputConfig({
68
- sections: Object.fromEntries(OUTPUT_SECTION_OPTIONS.map((option) => [option.value, selectedSet.has(option.value)]))
91
+ sections: Object.fromEntries(OUTPUT_SECTION_OPTIONS.map((option) => [option.value, selectedSet.has(option.value)])),
92
+ limits
69
93
  }, { dataRoot });
70
94
  logger(`│ Saved ContextOS prompt section config: ${outputConfigPath(dataRoot)}`);
71
95
  logger(`│ Enabled sections: ${enabledOutputSectionsLabel(saved)}`);
96
+ logger(`│ Suggest limits: ${outputConfigLimitsLabel(saved)}`);
72
97
  return saved;
73
98
  }
74
99
 
@@ -80,6 +105,16 @@ function normalizeOutputConfig(config = {}) {
80
105
  typeof config.sections?.[option.value] === "boolean"
81
106
  ? config.sections[option.value]
82
107
  : defaults.sections[option.value]
108
+ ])),
109
+ limits: Object.fromEntries(OUTPUT_LIMIT_OPTIONS.map((option) => [
110
+ option.value,
111
+ normalizeLimit(config.limits?.[option.value], option)
83
112
  ]))
84
113
  };
85
114
  }
115
+
116
+ function normalizeLimit(value, option) {
117
+ const number = Number(value);
118
+ if (!Number.isFinite(number)) return option.defaultValue;
119
+ return Math.max(0, Math.min(option.max, Math.trunc(number)));
120
+ }
@@ -3,15 +3,11 @@ import { appendJsonLine, writeJsonFile } from "./fs-utils.js";
3
3
  import { maybeAutoWarmWorkspace } from "./auto-warm.js";
4
4
  import { callCtxScoreContext } from "./ctx-mcp-client.js";
5
5
  import { resolveHookCwd } from "./hook-io.js";
6
- import { loadOutputConfig } from "./output-config.js";
6
+ import { loadOutputConfig, outputConfigLimits } from "./output-config.js";
7
7
  import { scoreContext as scoreContextDirect } from "./score-context.js";
8
8
  import fs from "node:fs";
9
9
  import path from "node:path";
10
10
 
11
- const PROMPT_FILE_LIMIT = 7;
12
- const PROMPT_SKILL_LIMIT = 7;
13
- const PROMPT_WORKFLOW_LIMIT = 2;
14
-
15
11
  export async function handlePromptPayload(
16
12
  payload,
17
13
  {
@@ -33,6 +29,8 @@ export async function handlePromptPayload(
33
29
  const cwd = resolvePromptTargetCwd({ cwd: hookCwd, prompt });
34
30
  const openFiles = payload.openFiles || payload.open_files || payload.files || [];
35
31
  const dataDir = dataPath ? path.dirname(dataPath) : undefined;
32
+ const effectiveOutputConfig = outputConfig || loadOutputConfig();
33
+ const promptLimits = outputConfigLimits(effectiveOutputConfig);
36
34
 
37
35
  let scored;
38
36
  try {
@@ -40,9 +38,9 @@ export async function handlePromptPayload(
40
38
  cwd,
41
39
  prompt,
42
40
  openFiles,
43
- maxFiles: PROMPT_FILE_LIMIT,
44
- maxSkills: PROMPT_SKILL_LIMIT,
45
- maxWorkflows: PROMPT_WORKFLOW_LIMIT
41
+ maxFiles: promptLimits.files,
42
+ maxSkills: promptLimits.skills,
43
+ maxWorkflows: promptLimits.workflows
46
44
  }, {
47
45
  dataDir: mcpDataDir || dataDir,
48
46
  timeoutMs: Number(process.env.CONTEXTOS_MCP_BRIDGE_TIMEOUT_MS || 2000)
@@ -53,9 +51,9 @@ export async function handlePromptPayload(
53
51
  cwd,
54
52
  prompt,
55
53
  openFiles,
56
- maxFiles: PROMPT_FILE_LIMIT,
57
- maxSkills: PROMPT_SKILL_LIMIT,
58
- maxWorkflows: PROMPT_WORKFLOW_LIMIT,
54
+ maxFiles: promptLimits.files,
55
+ maxSkills: promptLimits.skills,
56
+ maxWorkflows: promptLimits.workflows,
59
57
  dataDir: mcpDataDir || dataDir,
60
58
  embeddingTimeoutMs: Number(process.env.CONTEXTOS_HOOK_EMBEDDING_TIMEOUT_MS || 500),
61
59
  fileEmbeddingTimeoutMs: Number(process.env.CONTEXTOS_HOOK_FILE_EMBEDDING_TIMEOUT_MS || 1000)
@@ -76,10 +74,9 @@ export async function handlePromptPayload(
76
74
 
77
75
  if (scored.error) throw new Error(scored.error);
78
76
  const scoredRules = scored.scoredRules || [];
79
- const relevantFiles = (scored.suggestedFiles || []).slice(0, PROMPT_FILE_LIMIT);
80
- const suggestedSkills = (scored.suggestedSkills || []).slice(0, PROMPT_SKILL_LIMIT);
81
- const suggestedWorkflows = (scored.suggestedWorkflows || []).slice(0, PROMPT_WORKFLOW_LIMIT);
82
- const effectiveOutputConfig = outputConfig || loadOutputConfig();
77
+ const relevantFiles = (scored.suggestedFiles || []).slice(0, promptLimits.files);
78
+ const suggestedSkills = (scored.suggestedSkills || []).slice(0, promptLimits.skills);
79
+ const suggestedWorkflows = (scored.suggestedWorkflows || []).slice(0, promptLimits.workflows);
83
80
  const scheduled = scheduleContext({ rules: scoredRules, relevantFiles, suggestedSkills, suggestedWorkflows, outputConfig: effectiveOutputConfig });
84
81
  const contextEmptyReason = emptyContextReason({ scheduled, outputConfig: effectiveOutputConfig, injectContext });
85
82
  const autoWarm = autoWarmWorkspace({
@@ -37,7 +37,8 @@ export function setupSummaryLines({
37
37
  agents = DEFAULT_AGENTS,
38
38
  syncRules = true,
39
39
  syncSkills = true,
40
- promptSections = null
40
+ promptSections = null,
41
+ promptLimits = null
41
42
  } = {}) {
42
43
  const lines = [
43
44
  `Installation directory: ${cwd}`,
@@ -47,5 +48,6 @@ export function setupSummaryLines({
47
48
  `skillshare skill sync: ${syncSkills ? "enabled" : "skipped"}`
48
49
  ];
49
50
  if (promptSections !== null) lines.push(`Prompt sections shown: ${promptSections}`);
51
+ if (promptLimits !== null) lines.push(`Prompt suggest limits: ${promptLimits}`);
50
52
  return lines;
51
53
  }
@@ -20,9 +20,10 @@ const GENERIC_SKILL_TOKENS = new Set([
20
20
  "users", "when", "where", "whether", "with"
21
21
  ]);
22
22
  const SPECIALIZED_SKILL_TOKENS = new Set([
23
- "android", "authorization", "cicd", "eas", "expo", "frontend", "ios", "next", "nextjs",
24
- "mcp", "modelcontextprotocol", "postgres", "postgresql", "react", "react-native", "tailwind",
25
- "typescript", "ui"
23
+ "android", "architecture", "authorization", "cicd", "documentation", "docs", "document",
24
+ "eas", "expo", "frontend", "ios", "next", "nextjs", "mcp", "modelcontextprotocol",
25
+ "postgres", "postgresql", "react", "react-native", "readme", "tailwind", "typescript",
26
+ "ui", "wiki", "writer"
26
27
  ]);
27
28
 
28
29
  const scanCache = new Map();
@@ -312,6 +313,8 @@ function isSkillDomainEligible(normalizedPrompt, enriched, projectTokens = new S
312
313
  if (isMcpSkill(skillText) && !isMcpRelevantTask(normalizedPrompt, projectTokens)) return false;
313
314
  if (isOffensiveSecuritySkill(skillText) && !isSecurityTask(normalizedPrompt)) return false;
314
315
  if (isPlatformCommerceSkill(skillText) && !isPlatformCommerceTask(normalizedPrompt, skillText)) return false;
316
+ if (isDocumentProcessingSkill(skillText) && !isDocumentProcessingTask(normalizedPrompt, skillText)) return false;
317
+ if (isWorkspaceAutomationSkill(skillText) && !isWorkspaceAutomationTask(normalizedPrompt, skillText)) return false;
315
318
  if (!/\beas\b/.test(normalizedPrompt)) return true;
316
319
  if (!/\b(android|ios)\b/.test(skillText)) return true;
317
320
  return /\b(eas|expo|cicd)\b/.test(skillText);
@@ -319,6 +322,10 @@ function isSkillDomainEligible(normalizedPrompt, enriched, projectTokens = new S
319
322
 
320
323
  function skillIntentBonus(normalizedPrompt, enriched, projectTokens = new Set()) {
321
324
  const skillText = normalize(`${enriched.name} ${enriched.description}`);
325
+ if (isDocumentAuthoringTask(normalizedPrompt)
326
+ && /\b(documentation|document|docs|doc|readme|wiki|writer|writing|coauthor|technical documentation|architecture documentation|onboarding|office productivity)\b/.test(skillText)) {
327
+ return 0.48;
328
+ }
322
329
  if (isMcpRelevantTask(normalizedPrompt, projectTokens)
323
330
  && /\b(mcp|model context protocol|modelcontextprotocol|agent memory|tool developer|tool builder)\b/.test(skillText)) {
324
331
  return 0.48;
@@ -368,6 +375,20 @@ function skillRelevancePriority(normalizedPrompt, enriched, projectTokens = new
368
375
  const skillText = normalize(`${enriched.name} ${enriched.description}`);
369
376
  const skillName = normalize(enriched.name);
370
377
  let priority = 0;
378
+ if (isDocumentAuthoringTask(normalizedPrompt)) {
379
+ if (skillName === "doc coauthoring") priority += 1300;
380
+ if (skillName === "documentation") priority += 720;
381
+ if (skillName === "docs architect") priority += 700;
382
+ if (skillName === "readme") priority += 660;
383
+ if (skillName === "wiki page writer") priority += 640;
384
+ if (skillName === "wiki architect") priority += 620;
385
+ if (skillName === "wiki onboarding") priority += 600;
386
+ if (skillName === "writer" || skillName === "docx" || skillName === "office productivity") priority += 560;
387
+ if (skillName === "agents md") priority += 420;
388
+ if (/\b(code documentation doc generate|documentation generation doc generate|api documentation|api documenter|reference builder|architecture)\b/.test(skillText)) priority += 320;
389
+ if (/\b(documentation|document|docs|doc|readme|wiki|writer|writing|coauthor|technical documentation)\b/.test(skillText)) priority += 130;
390
+ if (/\b(mcp|model context protocol|metasploit|penetration|exploit)\b/.test(skillText)) priority -= 220;
391
+ }
371
392
  if (isMcpRelevantTask(normalizedPrompt, projectTokens)) {
372
393
  if (skillName === "mcp builder") priority += 760;
373
394
  if (skillName === "mcp management") priority += 740;
@@ -448,6 +469,11 @@ function isFrontendCheckoutTask(normalizedPrompt) {
448
469
  return /\b(modal|display|show|checkout|library|frontend|webapp|page|button)\b/.test(normalizedPrompt);
449
470
  }
450
471
 
472
+ function isDocumentAuthoringTask(normalizedPrompt) {
473
+ return /\b(create|write|edit|update|draft|generate|author|maintain|work on|produce)\b.*\b(document|documents|documentation|docs|doc|readme|wiki|workspace|workspaces|manual|guide|onboarding|spec|adr)\b/.test(normalizedPrompt)
474
+ || /\b(document|documents|documentation|docs|doc|readme|wiki|workspace|workspaces|manual|guide|onboarding|spec|adr)\b.*\b(create|write|edit|update|draft|generate|author|maintain|work on|produce)\b/.test(normalizedPrompt);
475
+ }
476
+
451
477
  function isMcpTask(normalizedPrompt) {
452
478
  return /\b(mcp|model context protocol|tool server|tools server|server tool|bridge|proxy)\b/.test(normalizedPrompt);
453
479
  }
@@ -489,6 +515,36 @@ function isPlatformCommerceTask(normalizedPrompt, skillText) {
489
515
  return true;
490
516
  }
491
517
 
518
+ function isDocumentProcessingSkill(skillText) {
519
+ return /\b(azure ai document|document intelligence|formrecognizer|document translation|cosmos db|azure cosmos|search documents|docusign)\b/.test(skillText);
520
+ }
521
+
522
+ function isDocumentProcessingTask(normalizedPrompt, skillText) {
523
+ if (/\bdocusign\b/.test(skillText)) return /\bdocusign|signature|envelope|sign\b/.test(normalizedPrompt);
524
+ if (/\bcosmos db|azure cosmos\b/.test(skillText)) return /\bcosmos|database|nosql|query|container\b/.test(normalizedPrompt);
525
+ if (/\bsearch documents\b/.test(skillText)) return /\bazure search|vector search|semantic search|index\b/.test(normalizedPrompt);
526
+ return /\bextract|ocr|analyze|translate|translation|form recognizer|document intelligence|azure\b/.test(normalizedPrompt);
527
+ }
528
+
529
+ function isWorkspaceAutomationSkill(skillText) {
530
+ return /\b(asana|bitbucket|slack|coda|google docs|google drive|google sheets|google slides|notion|telegram)\b/.test(skillText)
531
+ && /\b(automation|automate|workspace|workspaces|manage docs|documents)\b/.test(skillText);
532
+ }
533
+
534
+ function isWorkspaceAutomationTask(normalizedPrompt, skillText) {
535
+ if (/\basana\b/.test(skillText)) return /\basana\b/.test(normalizedPrompt);
536
+ if (/\bbitbucket\b/.test(skillText)) return /\bbitbucket\b/.test(normalizedPrompt);
537
+ if (/\bslack\b/.test(skillText)) return /\bslack\b/.test(normalizedPrompt);
538
+ if (/\bcoda\b/.test(skillText)) return /\bcoda\b/.test(normalizedPrompt);
539
+ if (/\bgoogle docs\b/.test(skillText)) return /\bgoogle docs\b/.test(normalizedPrompt);
540
+ if (/\bgoogle drive\b/.test(skillText)) return /\bgoogle drive\b/.test(normalizedPrompt);
541
+ if (/\bgoogle sheets\b/.test(skillText)) return /\bgoogle sheets\b/.test(normalizedPrompt);
542
+ if (/\bgoogle slides\b/.test(skillText)) return /\bgoogle slides\b/.test(normalizedPrompt);
543
+ if (/\bnotion\b/.test(skillText)) return /\bnotion\b/.test(normalizedPrompt);
544
+ if (/\btelegram\b/.test(skillText)) return /\btelegram\b/.test(normalizedPrompt);
545
+ return true;
546
+ }
547
+
492
548
  export function projectSkillHints({ cwd = process.cwd() } = {}) {
493
549
  const hints = new Set();
494
550
  const packagePaths = workspacePackagePaths(cwd);