@bcelep/capint 0.6.5 → 0.7.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.
Files changed (55) hide show
  1. package/AGENT.md +1 -1
  2. package/CHANGELOG.md +23 -0
  3. package/bin/capint.js +11 -2
  4. package/docs/PRD-capint.md +94 -0
  5. package/docs/PRD-v0.5-agent-capability-activation.md +10 -1
  6. package/docs/architecture-decisions.md +2 -0
  7. package/docs/capint-rehber.md +439 -0
  8. package/docs/capint-stack.md +118 -0
  9. package/docs/community.md +19 -0
  10. package/docs/conventions/daily-use.md +15 -1
  11. package/docs/eval/beginner-test-protocol.md +39 -0
  12. package/docs/eval/survey-template.md +16 -0
  13. package/docs/generated/README.md +6 -0
  14. package/docs/generated/execution-intent.v1.md +211 -0
  15. package/docs/generated/explanation.v1.md +127 -0
  16. package/docs/generated/explanation.v2.md +142 -0
  17. package/docs/generated/ui-api.v1.md +178 -0
  18. package/docs/maintainer-dogfood.md +3 -2
  19. package/docs/recipes/memory-graph-pairing.md +200 -0
  20. package/docs/skill-audit-runbook.md +32 -0
  21. package/locales/en.json +27 -0
  22. package/locales/tr.json +27 -0
  23. package/package.json +4 -2
  24. package/projections/session-start.md +14 -2
  25. package/schemas/execution-intent.v1.json +69 -0
  26. package/schemas/explanation.v2.json +51 -0
  27. package/schemas/ui-api.v1.json +85 -0
  28. package/scripts/generate-schema-docs.mjs +104 -0
  29. package/scripts/release-check.mjs +10 -0
  30. package/skills/prismx-skill-gateway/SKILL.md +10 -3
  31. package/src/commands/doctor.js +9 -0
  32. package/src/commands/feedback.js +29 -0
  33. package/src/commands/init.js +12 -0
  34. package/src/commands/providers.js +54 -0
  35. package/src/commands/skill.js +27 -0
  36. package/src/commands/stack.js +97 -0
  37. package/src/lib/audit.js +9 -1
  38. package/src/lib/contract.js +9 -0
  39. package/src/lib/doctor.js +69 -3
  40. package/src/lib/execution-policy.js +3 -1
  41. package/src/lib/explanation-plain.js +72 -0
  42. package/src/lib/explanation.js +26 -2
  43. package/src/lib/feedback.js +65 -0
  44. package/src/lib/i18n.js +43 -0
  45. package/src/lib/providers/doctor.js +205 -0
  46. package/src/lib/scaffold/index.js +2 -1
  47. package/src/lib/scaffold/presets.js +20 -0
  48. package/src/lib/skill-audit.js +141 -0
  49. package/src/lib/stack/index.js +259 -0
  50. package/src/lib/ui-server.js +92 -5
  51. package/templates/minimal/DONE.md +10 -0
  52. package/templates/minimal/GUNLUK.md +15 -1
  53. package/templates/minimal/docs/conventions/daily-use.md +3 -1
  54. package/ui/app.js +100 -10
  55. package/ui/index.html +22 -1
@@ -0,0 +1,85 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://capint.dev/schemas/ui-api.v1.json",
4
+ "title": "CapInt UI Wizard API",
5
+ "definitions": {
6
+ "RouteRequest": {
7
+ "type": "object",
8
+ "required": ["task"],
9
+ "properties": {
10
+ "task": { "type": "string", "minLength": 1 },
11
+ "locale": { "enum": ["tr", "en"] }
12
+ }
13
+ },
14
+ "RouteResponse": {
15
+ "type": "object",
16
+ "required": ["result", "text_report", "chat_copy"],
17
+ "properties": {
18
+ "result": {
19
+ "type": "object",
20
+ "required": ["execution_intent"],
21
+ "properties": {
22
+ "execution_intent": { "$ref": "execution-intent.v1.json" }
23
+ }
24
+ },
25
+ "text_report": { "type": "string" },
26
+ "chat_copy": { "type": "string" }
27
+ }
28
+ },
29
+ "HealthResponse": {
30
+ "type": "object",
31
+ "required": ["ok", "doctor", "status"],
32
+ "properties": {
33
+ "ok": { "type": "boolean" },
34
+ "doctor": { "type": "object" },
35
+ "status": {
36
+ "type": "object",
37
+ "properties": {
38
+ "skills_on_disk": { "type": "number" },
39
+ "core_skills": { "type": "number" },
40
+ "matrix_version": { "type": ["string", "null"] },
41
+ "capint_package": { "type": "string" }
42
+ }
43
+ }
44
+ }
45
+ },
46
+ "RecoverRequest": {
47
+ "type": "object",
48
+ "properties": {
49
+ "apply": { "type": "boolean", "default": false },
50
+ "latest": { "type": "boolean", "default": true }
51
+ }
52
+ },
53
+ "FeedbackRequest": {
54
+ "type": "object",
55
+ "properties": {
56
+ "rating": { "type": "integer", "minimum": 1, "maximum": 5 },
57
+ "comment": { "type": "string", "maxLength": 2000 },
58
+ "source": { "type": "string", "default": "ui" }
59
+ }
60
+ }
61
+ },
62
+ "paths": {
63
+ "/api/health": {
64
+ "get": { "response": { "$ref": "#/definitions/HealthResponse" } }
65
+ },
66
+ "/api/route": {
67
+ "post": {
68
+ "request": { "$ref": "#/definitions/RouteRequest" },
69
+ "response": { "$ref": "#/definitions/RouteResponse" }
70
+ }
71
+ },
72
+ "/api/recover": {
73
+ "post": {
74
+ "request": { "$ref": "#/definitions/RecoverRequest" },
75
+ "response": { "type": "object" }
76
+ }
77
+ },
78
+ "/api/feedback": {
79
+ "post": {
80
+ "request": { "$ref": "#/definitions/FeedbackRequest" },
81
+ "response": { "type": "object", "properties": { "ok": { "type": "boolean" } } }
82
+ }
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "crypto";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const root = path.join(__dirname, "..");
9
+ const schemaDir = path.join(root, "schemas");
10
+ const outDir = path.join(root, "docs", "generated");
11
+
12
+ const SCHEMAS = [
13
+ "execution-intent.v1.json",
14
+ "explanation.v2.json",
15
+ "ui-api.v1.json",
16
+ "explanation.v1.json"
17
+ ];
18
+
19
+ function propsTable(schema) {
20
+ const props = schema.properties || {};
21
+ const req = new Set(schema.required || []);
22
+ const lines = ["| Field | Type | Required |", "|-------|------|----------|"];
23
+ for (const [name, def] of Object.entries(props)) {
24
+ const type = def.enum ? def.enum.join(" \\| ") : def.type || def.$ref || "object";
25
+ lines.push(`| \`${name}\` | ${type} | ${req.has(name) ? "yes" : "no"} |`);
26
+ }
27
+ return lines.join("\n");
28
+ }
29
+
30
+ function renderSchemaDoc(fileName, schema) {
31
+ const title = schema.title || fileName;
32
+ return `# ${title}
33
+
34
+ > Auto-generated from \`schemas/${fileName}\`. Do not edit by hand.
35
+
36
+ **$id:** \`${schema.$id || ""}\`
37
+
38
+ ## Properties
39
+
40
+ ${propsTable(schema)}
41
+
42
+ ## Raw schema
43
+
44
+ \`\`\`json
45
+ ${JSON.stringify(schema, null, 2)}
46
+ \`\`\`
47
+ `;
48
+ }
49
+
50
+ function hashContent(content) {
51
+ return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
52
+ }
53
+
54
+ function generate() {
55
+ fs.mkdirSync(outDir, { recursive: true });
56
+ const written = [];
57
+ for (const file of SCHEMAS) {
58
+ const src = path.join(schemaDir, file);
59
+ if (!fs.existsSync(src)) continue;
60
+ const schema = JSON.parse(fs.readFileSync(src, "utf-8"));
61
+ const base = file.replace(/\.json$/, "");
62
+ const md = renderSchemaDoc(file, schema);
63
+ const dest = path.join(outDir, `${base}.md`);
64
+ fs.writeFileSync(dest, md, "utf-8");
65
+ written.push(dest);
66
+ }
67
+ const index = `# CapInt generated schema docs
68
+
69
+ ${SCHEMAS.filter((f) => fs.existsSync(path.join(schemaDir, f)))
70
+ .map((f) => `- [${f.replace(/\.json$/, "")}](./${f.replace(/\.json$/, "")}.md)`)
71
+ .join("\n")}
72
+ `;
73
+ fs.writeFileSync(path.join(outDir, "README.md"), index, "utf-8");
74
+ return written;
75
+ }
76
+
77
+ function check() {
78
+ const tmp = [];
79
+ for (const file of SCHEMAS) {
80
+ const src = path.join(schemaDir, file);
81
+ if (!fs.existsSync(src)) continue;
82
+ const schema = JSON.parse(fs.readFileSync(src, "utf-8"));
83
+ const expected = renderSchemaDoc(file, schema);
84
+ const dest = path.join(outDir, `${file.replace(/\.json$/, "")}.md`);
85
+ if (!fs.existsSync(dest)) {
86
+ console.error(`missing generated doc: ${path.relative(root, dest)}`);
87
+ process.exit(1);
88
+ }
89
+ const actual = fs.readFileSync(dest, "utf-8");
90
+ if (hashContent(actual) !== hashContent(expected)) {
91
+ console.error(`stale generated doc: ${path.relative(root, dest)} — run: node scripts/generate-schema-docs.mjs`);
92
+ process.exit(1);
93
+ }
94
+ tmp.push(dest);
95
+ }
96
+ console.log(`schema docs check ok (${tmp.length} files)`);
97
+ }
98
+
99
+ if (process.argv.includes("--check")) {
100
+ check();
101
+ } else {
102
+ const files = generate();
103
+ console.log(`generated ${files.length} schema docs → docs/generated/`);
104
+ }
@@ -21,6 +21,12 @@ const steps = [
21
21
  ["upgrade", ["node", "tests/upgrade.test.mjs"]],
22
22
  ["local-adapters", ["node", "tests/local-adapters.test.mjs"]],
23
23
  ["explanation", ["node", "tests/explanation.test.mjs"]],
24
+ ["providers-doctor", ["node", "tests/providers-doctor.test.mjs"]],
25
+ ["stack", ["node", "tests/stack.test.mjs"]],
26
+ ["skill-audit", ["node", "tests/skill-audit.test.mjs"]],
27
+ ["cli-ui-parity", ["node", "tests/cli-ui-parity.test.mjs"]],
28
+ ["generate-schema-docs", ["node", "scripts/generate-schema-docs.mjs"]],
29
+ ["schema-docs-check", ["node", "scripts/generate-schema-docs.mjs", "--check"]],
24
30
  ["workspace-boundary", ["node", "tests/workspace-boundary.test.mjs"]],
25
31
  ["skill-disable", ["node", "tests/skill-disable.test.mjs"]],
26
32
  ["recover", ["node", "tests/recover.test.mjs"]],
@@ -33,6 +39,10 @@ const steps = [
33
39
  ["pipeline-smoke", ["node", "bin/capint.js", "pipeline", "i18n çeviri", "--force"]],
34
40
  ["doctor-smoke", ["node", "bin/capint.js", "doctor"]],
35
41
  ["audit-smoke", ["node", "bin/capint.js", "audit", "--json"]],
42
+ ["skill-audit-smoke", ["node", "bin/capint.js", "skill", "audit", "--json"]],
43
+ ["feedback-smoke", ["node", "bin/capint.js", "feedback", "--summary", "--json"]],
44
+ ["providers-doctor-smoke", ["node", "bin/capint.js", "providers", "doctor", "--json"]],
45
+ ["stack-doctor-smoke", ["node", "bin/capint.js", "stack", "doctor", "--json"]],
36
46
  ["consult-smoke", ["node", "bin/capint.js", "consult", "debug login", "--json"]],
37
47
  ["status-smoke", ["node", "bin/capint.js", "status", "--json"]]
38
48
  ];
@@ -37,12 +37,19 @@ You are the **invisible skill router**. Every task request passes through you fi
37
37
  | `not_installed` | Do not pretend it exists. Use fallback or suggest `prismx skill add <name>`. |
38
38
  | `project_added` | Treat as first-class for this project after reading its `SKILL.md`. |
39
39
 
40
- ## PrismX Context Check (Before Routing)
40
+ ## Context Check (Before Routing)
41
+
42
+ **CapInt project (root):**
43
+ 1. Read `HANDOFF.md` if present → active task context
44
+ 2. Skim `DONE.md` → don't rebuild completed work
45
+ 3. Session rules: `AGENT.md` + gateway (this file)
46
+
47
+ **PrismX legacy (`.prismx/`):**
41
48
  1. Read `.prismx/HANDOFF.md` → current task context
42
49
  2. Read `.prismx/CURRENT_TASK.md` → today's focus (if present)
43
50
  3. Check `.prismx/DONE.md` → don't rebuild completed features
44
- 4. New session → prefer `session-context-primer` before heavy work
45
- 5. Check complexity weight → 🟢 skip brainstorming, 🔴 always brainstorm first
51
+
52
+ Then: new session → prefer `session-context-primer` before heavy work; check complexity weight → 🟢 skip brainstorming, 🔴 always brainstorm first
46
53
 
47
54
  ## Quick Routing Table
48
55
  | Keywords | Preferred Skill(s) | If Missing |
@@ -29,6 +29,15 @@ module.exports = async function doctor(_args, flags) {
29
29
  for (const i of report.issues) console.log(` x ${i}`);
30
30
  console.log("");
31
31
  }
32
+ if (report.actions?.length) {
33
+ console.log("Suggested actions:");
34
+ for (const a of report.actions) console.log(` → ${a.label} (${a.command})`);
35
+ console.log("");
36
+ }
37
+ if (report.summary_plain) {
38
+ console.log(`Summary: ${report.summary_plain}`);
39
+ console.log("");
40
+ }
32
41
  console.log(report.hint);
33
42
  console.log("");
34
43
 
@@ -0,0 +1,29 @@
1
+ module.exports = async function feedback(args, flags) {
2
+ const rootDir = process.cwd();
3
+ const { summarizeFeedback } = require("../lib/feedback");
4
+ const sub = args[0];
5
+
6
+ if (sub === "summary" || flags.summary || (!sub && flags.json)) {
7
+ const summary = summarizeFeedback(rootDir);
8
+ if (flags.json) {
9
+ console.log(JSON.stringify(summary, null, 2));
10
+ return;
11
+ }
12
+ console.log("\n## CapInt feedback (local)\n");
13
+ console.log(`Entries: ${summary.count}`);
14
+ console.log(`Rated: ${summary.rated_count}`);
15
+ console.log(`Average: ${summary.average_rating ?? "—"}`);
16
+ if (Object.keys(summary.by_source).length) {
17
+ console.log("By source:");
18
+ for (const [k, v] of Object.entries(summary.by_source)) console.log(` ${k}: ${v}`);
19
+ }
20
+ console.log("");
21
+ return;
22
+ }
23
+
24
+ console.log(`
25
+ capint feedback — local wizard feedback (stored under .capint/feedback/)
26
+
27
+ capint feedback --summary [--json]
28
+ `);
29
+ };
@@ -69,6 +69,18 @@ module.exports = async function init(args, flags) {
69
69
  console.log("\nIDE sync skipped (--no-ide-sync). Run: capint ide sync");
70
70
  }
71
71
  console.log("\nNext: open GUNLUK.md — then chat your first task (no terminal needed)");
72
+ console.log(" capint stack setup — workflows + graphify + agentmemory checklist");
72
73
  console.log(" capint route \"i18n\" (optional preview)");
74
+ console.log("Stack doc: docs/capint-stack.md");
73
75
  console.log("Conflict policy: existing files without capint managed blocks are never overwritten.\n");
76
+
77
+ if (flags.stack) {
78
+ const { runStackSetup, formatStackSummary } = require("../lib/stack");
79
+ const stackResult = await runStackSetup(rootDir, { installGraphifyRule: !flags["no-graphify-install"] });
80
+ console.log("## Stack setup (post-init)\n");
81
+ for (const step of stackResult.steps) {
82
+ console.log(` [${step.status}] ${step.id}: ${step.action}`);
83
+ }
84
+ console.log(`\n${formatStackSummary(stackResult.doctor)}\n`);
85
+ }
74
86
  };
@@ -0,0 +1,54 @@
1
+ module.exports = async function providers(args, flags) {
2
+ const sub = args[0];
3
+ const rootDir = process.cwd();
4
+ const { runProvidersDoctor } = require("../lib/providers/doctor");
5
+
6
+ if (sub !== "doctor") {
7
+ console.log(`
8
+ capint providers — memory/graph sidecar diagnostics
9
+
10
+ capint providers doctor [--json]
11
+
12
+ Recipe: docs/capint-stack.md (detail: docs/recipes/memory-graph-pairing.md)
13
+ `);
14
+ if (!sub) return;
15
+ console.error(`Unknown subcommand: ${sub}`);
16
+ process.exit(1);
17
+ }
18
+
19
+ const report = await runProvidersDoctor(rootDir);
20
+
21
+ if (flags.json) {
22
+ console.log(JSON.stringify(report, null, 2));
23
+ return;
24
+ }
25
+
26
+ console.log("\n## CapInt providers doctor\n");
27
+ console.log(`Recipe: ${report.recipe}\n`);
28
+
29
+ console.log("Environment:");
30
+ console.log(` CAPINT_LOCAL_CONTEXT: ${report.env.CAPINT_LOCAL_CONTEXT || "(unset)"}`);
31
+ console.log(` CAPINT_MEMORY: ${report.env.CAPINT_MEMORY || "(unset)"}`);
32
+ console.log(` CAPINT_GRAPH: ${report.env.CAPINT_GRAPH || "(unset)"}`);
33
+ console.log(` local_context hot-path: ${report.env.local_context_enabled ? "on" : "off"}\n`);
34
+
35
+ for (const tier of Object.values(report.tiers)) {
36
+ const icon = tier.status === "ok" ? "✓" : tier.status === "partial" || tier.status === "cli_only" ? "~" : "✗";
37
+ console.log(`${icon} ${tier.label} — ${tier.status}`);
38
+ if (tier.id === "tier0_local") {
39
+ for (const p of tier.paths) console.log(` ${p.exists ? "✓" : "✗"} ${p.path}`);
40
+ }
41
+ if (tier.id === "tier1_graphify" && tier.artifacts_found?.length) {
42
+ for (const p of tier.artifacts_found) console.log(` ✓ ${p}`);
43
+ }
44
+ if (tier.id === "tier2_agentmemory" && tier.health?.error) {
45
+ console.log(` ${tier.health.error}`);
46
+ }
47
+ }
48
+
49
+ if (report.recommendations.length) {
50
+ console.log("\nRecommendations:");
51
+ for (const r of report.recommendations) console.log(` - ${r}`);
52
+ }
53
+ console.log("");
54
+ };
@@ -18,6 +18,7 @@ capint skill — disable / enable / pin without deleting files
18
18
  capint skill disable <name>
19
19
  capint skill enable <name>
20
20
  capint skill pin <name> promote to registry project_added tier
21
+ capint skill audit [--json] orphan / ghost / unreferenced skill report
21
22
 
22
23
  Overlay: ${OVERLAY_PATH}
23
24
  `);
@@ -85,6 +86,32 @@ Overlay: ${OVERLAY_PATH}
85
86
  return;
86
87
  }
87
88
 
89
+ if (sub === "audit") {
90
+ const { runSkillAudit } = require("../lib/skill-audit");
91
+ const report = runSkillAudit(rootDir);
92
+ if (flags.json) {
93
+ console.log(JSON.stringify(report, null, 2));
94
+ if (!report.pass) process.exit(report.exit_code);
95
+ return;
96
+ }
97
+ console.log("\n## Skill audit\n");
98
+ console.log(`Result: ${report.pass ? "PASS" : "FAIL"}`);
99
+ console.log(
100
+ `Findings: ${report.summary.issues} issues, ${report.summary.warnings} warnings, ${report.summary.info} info`
101
+ );
102
+ console.log(`Skills on disk: ${report.summary.skills_on_disk} · Registry: ${report.summary.registry_count}\n`);
103
+ for (const f of report.findings) {
104
+ console.log(` [${f.level}] ${f.message}`);
105
+ }
106
+ if (report.recommendations.length) {
107
+ console.log("\nRecommendations:");
108
+ for (const r of report.recommendations) console.log(` - ${r}`);
109
+ }
110
+ console.log("");
111
+ if (!report.pass) process.exit(report.exit_code);
112
+ return;
113
+ }
114
+
88
115
  if (sub === "pin") {
89
116
  if (!name) {
90
117
  console.error("Usage: capint skill pin <name>");
@@ -0,0 +1,97 @@
1
+ const {
2
+ runStackDoctor,
3
+ runStackSetup,
4
+ syncGraphArtifacts,
5
+ detectGraphPaths,
6
+ formatStackSummary,
7
+ STACK_DOC
8
+ } = require("../lib/stack");
9
+
10
+ module.exports = async function stack(args, flags) {
11
+ const sub = args[0];
12
+ const rootDir = process.cwd();
13
+
14
+ if (!sub || sub === "help" || flags.help) {
15
+ console.log(`
16
+ capint stack — unified CapInt + workflows + graphify + agentmemory
17
+
18
+ capint stack doctor [--json] 4-layer health (replaces mental model of providers only)
19
+ capint stack setup [--json] post-init wiring checklist + .capint/stack.json
20
+ capint stack graph paths suggest /graphify folders for this repo
21
+ capint stack graph sync copy graphify-out/ → CapInt paths
22
+
23
+ Doc: ${STACK_DOC}
24
+ `);
25
+ return;
26
+ }
27
+
28
+ if (sub === "doctor") {
29
+ const report = await runStackDoctor(rootDir);
30
+ if (flags.json) {
31
+ console.log(JSON.stringify(report, null, 2));
32
+ return;
33
+ }
34
+ console.log("\n## CapInt stack doctor\n");
35
+ console.log(formatStackSummary(report));
36
+ console.log("");
37
+ if (report.graph_paths.suggested.length) {
38
+ console.log(`Suggested graphify: ${report.graph_paths.cursor_command}\n`);
39
+ }
40
+ if (report.recommendations.length) {
41
+ console.log("Next:");
42
+ for (const r of report.recommendations) console.log(` - ${r}`);
43
+ console.log("");
44
+ }
45
+ return;
46
+ }
47
+
48
+ if (sub === "setup") {
49
+ const result = await runStackSetup(rootDir, { installGraphifyRule: !flags["no-graphify-install"] });
50
+ if (flags.json) {
51
+ console.log(JSON.stringify(result, null, 2));
52
+ return;
53
+ }
54
+ console.log("\n## CapInt stack setup\n");
55
+ console.log(`Wrote ${result.manifest ? ".capint/stack.json" : "manifest"}\n`);
56
+ for (const step of result.steps) {
57
+ console.log(` [${step.status}] ${step.id}: ${step.action}`);
58
+ if (step.then) console.log(` then: ${step.then}`);
59
+ if (step.detail && step.status !== "ok") console.log(` (${step.detail})`);
60
+ }
61
+ console.log(`\n${formatStackSummary(result.doctor)}\n`);
62
+ return;
63
+ }
64
+
65
+ if (sub === "graph" && args[1] === "paths") {
66
+ const paths = detectGraphPaths(rootDir);
67
+ if (flags.json) {
68
+ console.log(JSON.stringify(paths, null, 2));
69
+ return;
70
+ }
71
+ console.log("\n## Graph paths for this project\n");
72
+ console.log(`Dirs found: ${paths.existing_dirs.join(", ") || "(none — will use .)"}`);
73
+ console.log(`Cursor: ${paths.cursor_command}`);
74
+ console.log(`Then: capint stack graph sync\n`);
75
+ return;
76
+ }
77
+
78
+ if (sub === "graph" && args[1] === "sync") {
79
+ const result = syncGraphArtifacts(rootDir);
80
+ if (flags.json) {
81
+ console.log(JSON.stringify(result, null, 2));
82
+ return;
83
+ }
84
+ if (!result.ok) {
85
+ console.error(result.error || "Nothing to sync");
86
+ process.exit(1);
87
+ }
88
+ console.log("\n## Graph synced to CapInt paths\n");
89
+ for (const a of result.actions) console.log(` ✓ ${a}`);
90
+ console.log("\nRun: capint stack doctor\n");
91
+ return;
92
+ }
93
+
94
+ console.error(`Unknown: capint stack ${args.join(" ")}`);
95
+ console.error("Try: capint stack help");
96
+ process.exit(1);
97
+ };
package/src/lib/audit.js CHANGED
@@ -2,6 +2,7 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { runDoctor } = require("./doctor");
4
4
  const { loadMatrix } = require("./route-engine");
5
+ const { runSkillAudit } = require("./skill-audit");
5
6
 
6
7
  const EXPECTED_AGENT_MARKERS = ["Execution Intent", "confirm", "parseIntent"];
7
8
 
@@ -89,6 +90,12 @@ function runAudit(rootDir) {
89
90
  findings.push({ level: "warning", code: "doctor", message: w });
90
91
  }
91
92
 
93
+ const skillAudit = runSkillAudit(rootDir);
94
+ for (const f of skillAudit.findings) {
95
+ findings.push(f);
96
+ }
97
+ recommendations.push(...skillAudit.recommendations);
98
+
92
99
  const issueCount = findings.filter((f) => f.level === "issue").length;
93
100
  const warningCount = findings.filter((f) => f.level === "warning").length;
94
101
 
@@ -100,7 +107,8 @@ function runAudit(rootDir) {
100
107
  summary: { issues: issueCount, warnings: warningCount, info: findings.filter((f) => f.level === "info").length },
101
108
  findings,
102
109
  recommendations: [...new Set(recommendations)],
103
- doctor_pass: doctor.pass
110
+ doctor_pass: doctor.pass,
111
+ skill_audit: skillAudit.summary
104
112
  };
105
113
  }
106
114
 
@@ -36,6 +36,15 @@ function validateExplanationShape(explanation, errors) {
36
36
  if (typeof explanation.reason_summary !== "string") {
37
37
  errors.push("explanation.reason_summary must be string");
38
38
  }
39
+ if (explanation.reason_plain != null && typeof explanation.reason_plain !== "string") {
40
+ errors.push("explanation.reason_plain must be string when present");
41
+ }
42
+ if (explanation.tooltip != null && typeof explanation.tooltip !== "string") {
43
+ errors.push("explanation.tooltip must be string when present");
44
+ }
45
+ if (explanation.locale != null && !["tr", "en"].includes(explanation.locale)) {
46
+ errors.push("explanation.locale must be tr or en when present");
47
+ }
39
48
  if (!Array.isArray(explanation.files_to_read)) {
40
49
  errors.push("explanation.files_to_read must be array");
41
50
  }
package/src/lib/doctor.js CHANGED
@@ -1,8 +1,9 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { loadMatrix } = require("./route-engine");
4
- const { validateExecutionIntent } = require("./contract");
5
4
  const { loadDisabledSkills } = require("./disabled-skills");
5
+ const { listBackups } = require("./recover");
6
+ const { resolveLocale, t } = require("./i18n");
6
7
 
7
8
  function loadRegistry(rootDir) {
8
9
  const p = path.join(rootDir, "registry.json");
@@ -22,7 +23,60 @@ function skillsOnDisk(rootDir) {
22
23
  .filter((n) => fs.existsSync(path.join(skillsDir, n, "SKILL.md")));
23
24
  }
24
25
 
25
- function runDoctor(rootDir) {
26
+ function buildDoctorActions({ issues, warnings, rootDir, locale }) {
27
+ const loc = resolveLocale(locale);
28
+ const actions = [];
29
+ const hasConfigIssue = issues.some(
30
+ (i) => i.includes("matrix") || i.includes("registry") || i.includes("parse error")
31
+ );
32
+ const { groups } = listBackups(rootDir);
33
+
34
+ if (hasConfigIssue && groups.length) {
35
+ actions.push({
36
+ id: "recover_latest",
37
+ safe: true,
38
+ command: "capint recover --latest --apply",
39
+ label_tr: t("ui.recover.apply", "tr"),
40
+ label_en: t("ui.recover.apply", "en"),
41
+ label: t("ui.recover.apply", loc)
42
+ });
43
+ }
44
+
45
+ for (const w of warnings) {
46
+ const m = w.match(/^core skill disabled: (\S+)/);
47
+ if (m) {
48
+ actions.push({
49
+ id: "enable_core",
50
+ safe: true,
51
+ skill: m[1],
52
+ command: `capint skill enable ${m[1]}`,
53
+ label_tr: `Core skill'i aç: ${m[1]}`,
54
+ label_en: `Re-enable core skill: ${m[1]}`,
55
+ label: loc === "en" ? `Re-enable core skill: ${m[1]}` : `Core skill'i aç: ${m[1]}`
56
+ });
57
+ }
58
+ }
59
+
60
+ return actions;
61
+ }
62
+
63
+ function buildSummaryPlain({ pass, issues, warnings, locale }) {
64
+ const loc = resolveLocale(locale);
65
+ if (pass && !warnings.length) return t("doctor.summary.ok", loc);
66
+ if (issues.some((i) => i.includes("matrix") || i.includes("registry"))) {
67
+ return t("doctor.summary.registry", loc);
68
+ }
69
+ if (warnings.some((w) => w.includes("disabled"))) {
70
+ return t("doctor.summary.disabled", loc);
71
+ }
72
+ if (!pass) {
73
+ return loc === "en" ? "Configuration issues found — see details below." : "Yapılandırma sorunları var — aşağıdaki detaylara bak.";
74
+ }
75
+ return loc === "en" ? "Healthy with minor warnings." : "Genel olarak sağlıklı; küçük uyarılar var.";
76
+ }
77
+
78
+ function runDoctor(rootDir, options = {}) {
79
+ const locale = resolveLocale(options.locale);
26
80
  const issues = [];
27
81
  const warnings = [];
28
82
  const ok = [];
@@ -44,6 +98,11 @@ function runDoctor(rootDir) {
44
98
  ...(registry.optional || []),
45
99
  ...(registry.project_added || [])
46
100
  ]);
101
+ for (const name of disk) {
102
+ if (!allReg.has(name)) {
103
+ warnings.push(`Skill on disk but not in registry: ${name}`);
104
+ }
105
+ }
47
106
  for (const name of registry.core || []) {
48
107
  if (!disk.has(name)) warnings.push(`core skill missing on disk: ${name}`);
49
108
  }
@@ -100,6 +159,9 @@ function runDoctor(rootDir) {
100
159
  hint = "Re-enable core skills: capint skill enable <name>";
101
160
  }
102
161
 
162
+ const actions = buildDoctorActions({ issues, warnings, rootDir, locale });
163
+ const summary_plain = buildSummaryPlain({ pass, issues, warnings, locale });
164
+
103
165
  return {
104
166
  pass,
105
167
  exit_code: pass ? 0 : 1,
@@ -107,7 +169,11 @@ function runDoctor(rootDir) {
107
169
  warnings,
108
170
  ok,
109
171
  auto_fix: false,
110
- hint
172
+ hint,
173
+ actions,
174
+ summary_plain,
175
+ summary_plain_tr: buildSummaryPlain({ pass, issues, warnings, locale: "tr" }),
176
+ summary_plain_en: buildSummaryPlain({ pass, issues, warnings, locale: "en" })
111
177
  };
112
178
  }
113
179
 
@@ -3,6 +3,7 @@ const path = require("path");
3
3
  const { resolveOrchestration } = require("./orchestration");
4
4
  const { disabledSet, overlayParseError } = require("./disabled-skills");
5
5
  const { buildExplanation } = require("./explanation");
6
+ const { resolveLocale } = require("./i18n");
6
7
  const { matchKeywords } = require("./intent-parser");
7
8
  const { defaultConfirmOverride } = require("./env-flags");
8
9
  const { detectWorkspaceBoundary } = require("./workspace-boundary");
@@ -157,7 +158,8 @@ function buildExecutionPolicy({ rawText, routeResult, memoryStrategy, matrix, pa
157
158
  rootDir,
158
159
  matchKeywords,
159
160
  resolutionStatus: installStatus.resolution_status,
160
- confirmDefaultOption: confirmBlock.default_option
161
+ confirmDefaultOption: confirmBlock.default_option,
162
+ locale: resolveLocale()
161
163
  });
162
164
 
163
165
  const out = {