ctxora 6.3.0 → 6.4.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxora",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "description": "CTXORA repository context, memory, handoff, and diagnostics workflows.",
5
5
  "homepage": "https://ctxora-landing.vercel.app/",
6
6
  "repository": "https://github.com/nguyentrunghieutcu/ctxora-engine",
package/README.md CHANGED
@@ -180,10 +180,12 @@ npx ctxora skills preview --workspace . --profile developer \
180
180
  --add-module security --remove-skill security-scan
181
181
  npx ctxora skills install --workspace . --profile developer \
182
182
  --add-module security --remove-skill security-scan
183
+ ctxora skills install --workspace . --profile developer \
184
+ --target codex --target claude --target cursor --target gemini
183
185
  ctxora skills route "Fix React hydration performance" --workspace . --profile developer
184
186
  ```
185
187
 
186
- The default installation directory is `.agents/skills`; use `--output` for another host-specific directory. Preview never mutates files. Install refuses to overwrite modified skills unless `--force` is explicit, and `--prune` removes only unchanged skills previously installed by CTXORA. See `THIRD_PARTY_NOTICES.md` for provenance and licensing.
188
+ The default target is Codex-compatible `.agents/skills`. Use repeatable `--target codex|claude|cursor|gemini|opencode`, `--target all`, or `--output` for a custom directory. Preview never mutates files. Install refuses to overwrite modified skills unless `--force` is explicit, and `--prune` removes only unchanged skills previously installed by CTXORA. See `THIRD_PARTY_NOTICES.md` for provenance and licensing.
187
189
 
188
190
  ### Commands and agent roles
189
191
 
package/README.vi.md CHANGED
@@ -180,9 +180,11 @@ npx ctxora skills preview --workspace . --profile developer \
180
180
  --add-module security --remove-skill security-scan
181
181
  npx ctxora skills install --workspace . --profile developer \
182
182
  --add-module security --remove-skill security-scan
183
+ ctxora skills install --workspace . --profile developer \
184
+ --target codex --target claude --target cursor --target gemini
183
185
  ```
184
186
 
185
- Thư mục cài mặc định là `.agents/skills`; dùng `--output` cho thư mục skill riêng của host khác. Preview không sửa file. Install không ghi đè skill đã chỉnh sửa nếu chưa truyền `--force`; `--prune` chỉ xóa skill chưa bị sửa và trước đó do CTXORA cài. Xem provenance và license tại `THIRD_PARTY_NOTICES.md`.
187
+ Target mặc định là `.agents/skills` tương thích Codex. Có thể lặp `--target codex|claude|cursor|gemini|opencode`, dùng `--target all`, hoặc `--output` cho thư mục tùy chỉnh. Preview không sửa file. Install không ghi đè skill đã chỉnh sửa nếu chưa truyền `--force`; `--prune` chỉ xóa skill chưa bị sửa và trước đó do CTXORA cài. Xem provenance và license tại `THIRD_PARTY_NOTICES.md`.
186
188
 
187
189
  ### Command và agent role
188
190
 
@@ -193,6 +195,7 @@ CTXORA cũng cung cấp các template workflow chủ động, lấy cảm hứng
193
195
  | Command | Mục đích | Khả năng CTXORA |
194
196
  |---|---|---|
195
197
  | `/ctxora:context <task>` | Lấy bằng chứng repository trước khi code | `plan_context`, `retrieve_context`, `prepare_context` |
198
+ | `/ctxora:route <task>` | Tự chọn skill trong profile và học sau validation | `route_skills`, `skill_feedback` |
196
199
  | `/ctxora:plan <task>` | Lập kế hoạch dựa trên context | `plan_context`, `retrieve_context` |
197
200
  | `/ctxora:review [scope]` | Review thay đổi với context repository | `retrieve_context` |
198
201
  | `/ctxora:health` | Kiểm tra workspace và index | `doctor`, `context-score`, `inspect` |
package/bin/ctxora.mjs CHANGED
@@ -6,7 +6,7 @@ import { dirname, join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { spawnSync } from "node:child_process";
8
8
 
9
- const PACKAGE_VERSION = "6.3.0";
9
+ const PACKAGE_VERSION = "6.4.0";
10
10
  const PYTHON_RANGE = "3.10-3.13";
11
11
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
12
12
 
package/commands/route.md CHANGED
@@ -3,4 +3,4 @@ description: Automatically rank the installed ECC skills for a task.
3
3
  argument-hint: <task>
4
4
  ---
5
5
 
6
- Call `route_skills` for the registered workspace and `$ARGUMENTS`. Use the highest-ranked relevant recommendations, read their `SKILL.md` files before acting, and retain the returned `route_id`. After validation, call `skill_feedback` with the skills actually used and the outcome. Do not execute bundled scripts or external actions merely because a routed skill mentions them.
6
+ Call `route_skills` for the registered workspace and `$ARGUMENTS`. Use the highest-ranked relevant recommendations, read their `SKILL.md` files before acting, and retain the returned `route_id` in task state. After validation passes, automatically call `skill_feedback` with outcome `success` and the skills actually used. Use `failure` only when the failed result is attributable to the guidance; if the user replaces the selected skill, use `corrected`. Do not ask the user to submit routine feedback manually, and do not execute bundled scripts or external actions merely because a routed skill mentions them.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxora",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "description": "Local-first context engine for coding agents.",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ctxora-engine"
7
- version = "6.3.0"
7
+ version = "6.4.0"
8
8
  description = "Local-first context engine for coding agents."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -7,7 +7,7 @@ description: Capture evidence-backed project lessons in CTXORA memory and promot
7
7
 
8
8
  CTXORA learning is deliberate and memory-based; it does not require background hooks.
9
9
 
10
- Skill-routing learning is separate from durable memory. Keep the `route_id` returned by `route_skills` or `prepare_context`, then call `skill_feedback` after validation. CTXORA stores only a task fingerprint, matched catalog terms, bounded counters, and decayed associations; it never stores raw tasks or transcripts.
10
+ Skill-routing learning is separate from durable memory. Keep the `route_id` returned by `route_skills` or `prepare_context` in task state. After validation passes, automatically call `skill_feedback` with `success` and the skills actually used; use `failure` only when the failed result is attributable to the guidance, and `corrected` when the user replaces the routed skill. Do not ask the user to submit routine feedback manually. CTXORA stores only a task fingerprint, matched catalog terms, bounded counters, and decayed associations; it never stores raw tasks or transcripts.
11
11
 
12
12
  Inspect connected tool schemas and resolve the registered workspace ID before using `memory_search` or `memory_save`. If tools are unavailable, present a proposed lesson and explicitly report that it was not saved. Do not write directly into memory databases or ECC vaults. Do not capture raw session transcripts or install observers as part of this skill.
13
13
 
@@ -47,7 +47,7 @@ class ContextService:
47
47
  if self.skill_router is not None:
48
48
  try:
49
49
  routed = self.skill_router.route(request.workspace_id, request.query, top_k=3, include_instructions=False, token_budget=0)
50
- skill_diagnostics.update({"route_id": routed["route_id"], "profile": routed["profile"], "recommendations": routed["recommendations"]})
50
+ skill_diagnostics.update({"route_id": routed["route_id"], "profile": routed["profile"], "recommendations": routed["recommendations"], "feedback_policy": routed["feedback_policy"]})
51
51
  except ValueError as error:
52
52
  skill_diagnostics.update({"recommendations": [], "routing_error": str(error)})
53
53
  elapsed_ms = (time.perf_counter() - started) * 1000
@@ -119,7 +119,13 @@ def build_parser() -> argparse.ArgumentParser:
119
119
  action_parser.add_argument("--remove-module", action="append", default=[])
120
120
  action_parser.add_argument("--add-skill", action="append", default=[])
121
121
  action_parser.add_argument("--remove-skill", action="append", default=[])
122
- action_parser.add_argument("--output", default=".agents/skills")
122
+ action_parser.add_argument("--output", default="")
123
+ action_parser.add_argument(
124
+ "--target",
125
+ action="append",
126
+ choices=("codex", "claude", "cursor", "gemini", "opencode", "all"),
127
+ default=[],
128
+ )
123
129
  action_parser.add_argument("--force", action="store_true")
124
130
  action_parser.add_argument("--prune", action="store_true")
125
131
  if action == "install":
@@ -191,13 +197,25 @@ def _main(argv: list[str] | None = None) -> int:
191
197
  tuple(args.remove_skill),
192
198
  )
193
199
  dry_run = args.skills_action == "preview" or args.dry_run
194
- result = catalog.install(root, args.output, selection, force=args.force, prune=args.prune, dry_run=dry_run)
195
- result["status"] = "conflict" if result["conflicts"] else ("planned" if dry_run else "installed")
196
- result["selection"] = selection.to_dict()
197
- if not dry_run and not result["conflicts"]:
200
+ installs = []
201
+ conflicts = []
202
+ for target, output in catalog.install_outputs(tuple(args.target), args.output):
203
+ installed = catalog.install(
204
+ root, output, selection, force=args.force, prune=args.prune, dry_run=dry_run
205
+ )
206
+ installed["target"] = target
207
+ installs.append(installed)
208
+ conflicts.extend(f"{target}:{skill}" for skill in installed["conflicts"])
209
+ result = {
210
+ "status": "conflict" if conflicts else ("planned" if dry_run else "installed"),
211
+ "selection": selection.to_dict(),
212
+ "installs": installs,
213
+ "conflicts": conflicts,
214
+ }
215
+ if not dry_run and not conflicts:
198
216
  result["profile_config"] = str(catalog.write_selection(root, selection))
199
217
  _print(result)
200
- return 2 if result["conflicts"] else 0
218
+ return 2 if conflicts else 0
201
219
  if args.command == "setup":
202
220
  config = workspace_data_dir(root) / "config.toml"
203
221
  config.parent.mkdir(parents=True, exist_ok=True)
@@ -9,6 +9,14 @@ from dataclasses import dataclass
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
+ SKILL_INSTALL_TARGETS = {
13
+ "codex": ".agents/skills",
14
+ "claude": ".claude/skills",
15
+ "cursor": ".cursor/skills",
16
+ "gemini": ".gemini/skills",
17
+ "opencode": ".opencode/skills",
18
+ }
19
+
12
20
 
13
21
  @dataclass(frozen=True)
14
22
  class SkillSelection:
@@ -95,6 +103,20 @@ class SkillCatalog:
95
103
  def source_root(self) -> Path:
96
104
  return Path(__file__).with_name("ecc")
97
105
 
106
+ @staticmethod
107
+ def install_outputs(targets: tuple[str, ...], output: str = "") -> tuple[tuple[str, str], ...]:
108
+ if output and targets:
109
+ raise ValueError("use either --output or --target, not both")
110
+ if output:
111
+ return (("custom", output),)
112
+ selected = list(targets or ("codex",))
113
+ if "all" in selected:
114
+ selected = list(SKILL_INSTALL_TARGETS)
115
+ unknown = sorted(set(selected) - set(SKILL_INSTALL_TARGETS))
116
+ if unknown:
117
+ raise ValueError(f"unknown skill install target: {unknown[0]}")
118
+ return tuple((name, SKILL_INSTALL_TARGETS[name]) for name in dict.fromkeys(selected))
119
+
98
120
  def install(
99
121
  self,
100
122
  workspace: str | Path,
@@ -165,6 +165,12 @@ class SkillRouter:
165
165
  "candidate_count": len(candidates),
166
166
  "recommendations": recommendations,
167
167
  "learning": {"project_scoped": True, "raw_tasks_stored": False},
168
+ "feedback_policy": {
169
+ "mode": "after_validation",
170
+ "retain_route_id": True,
171
+ "success_requires_validation": True,
172
+ "user_correction_outcome": "corrected",
173
+ },
168
174
  }
169
175
 
170
176
  def feedback(