@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,259 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+ const { runProvidersDoctor } = require("../providers/doctor");
5
+
6
+ const STACK_DOC = "docs/capint-stack.md";
7
+ const STACK_MANIFEST = ".capint/stack.json";
8
+ const WORKFLOW_ANCHORS = [
9
+ { path: "workflows/forge.md", label: "forge gate (zero-regression)" },
10
+ { path: "workflows/challenge.md", label: "challenge gate" },
11
+ { path: "workflows/genesis.md", label: "genesis / design-first" },
12
+ { path: "workflows/blueprint.md", label: "blueprint / tasks" }
13
+ ];
14
+
15
+ const GRAPH_PATH_CANDIDATES = ["src", "app", "lib", "components", "packages"];
16
+
17
+ function fileExists(rootDir, rel) {
18
+ return fs.existsSync(path.join(rootDir, rel));
19
+ }
20
+
21
+ function countMarkdown(dir) {
22
+ if (!fs.existsSync(dir)) return 0;
23
+ return fs.readdirSync(dir).filter((f) => f.endsWith(".md")).length;
24
+ }
25
+
26
+ function tierWorkflows(rootDir) {
27
+ const wfDir = path.join(rootDir, "workflows");
28
+ const count = countMarkdown(wfDir);
29
+ const anchors = WORKFLOW_ANCHORS.map((a) => ({
30
+ ...a,
31
+ exists: fileExists(rootDir, a.path)
32
+ }));
33
+ const coreOk = anchors.filter((a) =>
34
+ ["workflows/forge.md", "workflows/challenge.md"].includes(a.path)
35
+ ).every((a) => a.exists);
36
+
37
+ let status = "missing";
38
+ if (count >= 4 && coreOk) status = "ok";
39
+ else if (count > 0) status = "partial";
40
+
41
+ return {
42
+ id: "layer_workflows",
43
+ label: "ANWS-style workflows (decisions, forge gate, drift)",
44
+ status,
45
+ workflow_count: count,
46
+ anchors,
47
+ chat: "Use /genesis /blueprint /challenge /forge in chat — not capint CLI"
48
+ };
49
+ }
50
+
51
+ function tierRouting(rootDir) {
52
+ const files = ["GUNLUK.md", "AGENT.md", "HANDOFF.md", "design.md", "skill-routing-matrix.json"];
53
+ const paths = files.map((p) => ({ path: p, exists: fileExists(rootDir, p) }));
54
+ const hasCore = paths.filter((p) => ["GUNLUK.md", "AGENT.md", "skill-routing-matrix.json"].includes(p.path)).every(
55
+ (p) => p.exists
56
+ );
57
+ return {
58
+ id: "layer_routing",
59
+ label: "CapInt routing + HANDOFF contract",
60
+ status: hasCore ? "ok" : fileExists(rootDir, "AGENT.md") ? "partial" : "missing",
61
+ paths,
62
+ enable: "capint init — daily chat via GUNLUK.md"
63
+ };
64
+ }
65
+
66
+ function detectGraphPaths(rootDir) {
67
+ const existing = GRAPH_PATH_CANDIDATES.filter((name) => {
68
+ const p = path.join(rootDir, name);
69
+ return fs.existsSync(p) && fs.statSync(p).isDirectory();
70
+ });
71
+
72
+ let suggested = [];
73
+ if (existing.includes("src")) {
74
+ suggested = ["./src"];
75
+ } else if (existing.includes("app") && existing.includes("lib")) {
76
+ suggested = ["./app", "./lib", "./components"].filter((rel) => fileExists(rootDir, rel.replace(/^\.\//, "")));
77
+ } else if (existing.length) {
78
+ suggested = existing.slice(0, 3).map((d) => `./${d}`);
79
+ } else {
80
+ suggested = ["."];
81
+ }
82
+
83
+ const cursorCmd = `/graphify ${suggested.join(" ")}`;
84
+ return { existing_dirs: existing, suggested, cursor_command: cursorCmd };
85
+ }
86
+
87
+ function syncGraphArtifacts(rootDir) {
88
+ const outDir = path.join(rootDir, "graphify-out");
89
+ const reportSrc = path.join(outDir, "GRAPH_REPORT.md");
90
+ const jsonSrc = path.join(outDir, "graph.json");
91
+ const actions = [];
92
+
93
+ if (!fs.existsSync(reportSrc) && !fs.existsSync(jsonSrc)) {
94
+ return { ok: false, error: "graphify-out/ not found — run /graphify in Cursor first", actions };
95
+ }
96
+
97
+ if (fs.existsSync(reportSrc)) {
98
+ fs.copyFileSync(reportSrc, path.join(rootDir, "GRAPH_REPORT.md"));
99
+ actions.push("GRAPH_REPORT.md");
100
+ }
101
+ if (fs.existsSync(jsonSrc)) {
102
+ const destDir = path.join(rootDir, ".capint", "graph");
103
+ fs.mkdirSync(destDir, { recursive: true });
104
+ fs.copyFileSync(jsonSrc, path.join(destDir, "graph.json"));
105
+ actions.push(".capint/graph/graph.json");
106
+ }
107
+
108
+ return { ok: actions.length > 0, actions };
109
+ }
110
+
111
+ function tryGraphifyCursorInstall(rootDir) {
112
+ try {
113
+ execSync("graphify install cursor --project", {
114
+ cwd: rootDir,
115
+ stdio: "pipe",
116
+ encoding: "utf-8"
117
+ });
118
+ return { ok: true, path: path.join(rootDir, ".cursor", "rules", "graphify.mdc") };
119
+ } catch (e) {
120
+ return { ok: false, error: (e.stderr || e.message || "graphify install failed").trim() };
121
+ }
122
+ }
123
+
124
+ function writeStackManifest(rootDir, report) {
125
+ const manifest = {
126
+ version: 1,
127
+ updated_at: new Date().toISOString(),
128
+ doc: STACK_DOC,
129
+ layers: {
130
+ workflows: report.layers.layer_workflows.status,
131
+ routing: report.layers.layer_routing.status,
132
+ graph: report.providers.tiers.tier1_graphify.status,
133
+ memory: report.providers.tiers.tier2_agentmemory.status
134
+ },
135
+ graph_paths: report.graph_paths
136
+ };
137
+ fs.mkdirSync(path.join(rootDir, ".capint"), { recursive: true });
138
+ fs.writeFileSync(path.join(rootDir, STACK_MANIFEST), `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
139
+ return manifest;
140
+ }
141
+
142
+ async function runStackDoctor(rootDir) {
143
+ const providers = await runProvidersDoctor(rootDir);
144
+ providers.recipe = STACK_DOC;
145
+
146
+ const layers = {
147
+ layer_workflows: tierWorkflows(rootDir),
148
+ layer_routing: tierRouting(rootDir)
149
+ };
150
+
151
+ const graph_paths = detectGraphPaths(rootDir);
152
+
153
+ const stackReady =
154
+ layers.layer_routing.status === "ok" &&
155
+ layers.layer_workflows.status !== "missing" &&
156
+ providers.tiers.tier1_graphify.status === "ok" &&
157
+ providers.tiers.tier2_agentmemory.status === "ok";
158
+
159
+ const recommendations = [];
160
+ if (layers.layer_routing.status !== "ok") recommendations.push("Run: capint init");
161
+ if (layers.layer_workflows.status === "missing") {
162
+ recommendations.push("Init with --bundle full (workflows/) or copy workflows from CapInt package.");
163
+ }
164
+ if (providers.tiers.tier1_graphify.status !== "ok") {
165
+ recommendations.push(`Cursor: ${graph_paths.cursor_command} then capint stack graph sync`);
166
+ }
167
+ if (providers.tiers.tier2_agentmemory.status !== "ok") {
168
+ recommendations.push("Machine: agentmemory (background) + Cursor MCP — see capint stack setup");
169
+ }
170
+ if (!providers.env.local_context_enabled) {
171
+ recommendations.push("Optional: CAPINT_LOCAL_CONTEXT=1 on route for file + graph excerpts.");
172
+ }
173
+
174
+ return {
175
+ mode: "read_only",
176
+ doc: STACK_DOC,
177
+ stack_ready: stackReady,
178
+ layers,
179
+ graph_paths,
180
+ providers,
181
+ env: providers.env,
182
+ recommendations,
183
+ daily_use: {
184
+ chat: "GUNLUK.md — natural language tasks",
185
+ heavy: "/blueprint then /forge (challenge gate before forge)",
186
+ memory: "agentmemory MCP + HANDOFF/DONE at session end",
187
+ graph: graph_paths.cursor_command
188
+ }
189
+ };
190
+ }
191
+
192
+ async function runStackSetup(rootDir, { installGraphifyRule = true } = {}) {
193
+ const doctorBefore = await runStackDoctor(rootDir);
194
+ const steps = [];
195
+
196
+ if (doctorBefore.layers.layer_routing.status !== "ok") {
197
+ steps.push({ id: "init", status: "required", action: "capint init [--bundle full]" });
198
+ } else {
199
+ steps.push({ id: "init", status: "ok", action: "CapInt scaffold present" });
200
+ }
201
+
202
+ if (installGraphifyRule && !fileExists(rootDir, ".cursor/rules/graphify.mdc")) {
203
+ const inst = tryGraphifyCursorInstall(rootDir);
204
+ steps.push({
205
+ id: "graphify_cursor",
206
+ status: inst.ok ? "ok" : "manual",
207
+ action: inst.ok ? "graphify install cursor --project" : "uv tool install graphifyy && graphify install cursor --project",
208
+ detail: inst.ok ? inst.path : inst.error
209
+ });
210
+ } else {
211
+ steps.push({ id: "graphify_cursor", status: "ok", action: ".cursor/rules/graphify.mdc" });
212
+ }
213
+
214
+ steps.push({
215
+ id: "graph_build",
216
+ status: doctorBefore.providers.tiers.tier1_graphify.status === "ok" ? "ok" : "manual",
217
+ action: doctorBefore.graph_paths.cursor_command,
218
+ then: "capint stack graph sync"
219
+ });
220
+
221
+ steps.push({
222
+ id: "agentmemory",
223
+ status: doctorBefore.providers.tiers.tier2_agentmemory.status === "ok" ? "ok" : "manual",
224
+ action: "agentmemory (separate terminal) + ~/.cursor/mcp.json agentmemory block",
225
+ doc: "https://github.com/rohitg00/agentmemory#other-agents"
226
+ });
227
+
228
+ const manifest = writeStackManifest(rootDir, doctorBefore);
229
+
230
+ return {
231
+ doc: STACK_DOC,
232
+ manifest,
233
+ steps,
234
+ doctor: await runStackDoctor(rootDir)
235
+ };
236
+ }
237
+
238
+ function formatStackSummary(report) {
239
+ const lines = [];
240
+ lines.push("CapInt stack (4 layers):");
241
+ lines.push(` 1. Workflows ${report.layers.layer_workflows.status} — ANWS-style gates (/forge, /challenge)`);
242
+ lines.push(` 2. Routing ${report.layers.layer_routing.status} — capint route + HANDOFF/GUNLUK`);
243
+ lines.push(` 3. Graph ${report.providers.tiers.tier1_graphify.status} — graphify → CapInt reads artifacts`);
244
+ lines.push(` 4. Memory ${report.providers.tiers.tier2_agentmemory.status} — agentmemory sidecar + MCP`);
245
+ lines.push(` Stack ready: ${report.stack_ready ? "yes" : "no"} — doc: ${report.doc}`);
246
+ return lines.join("\n");
247
+ }
248
+
249
+ module.exports = {
250
+ STACK_DOC,
251
+ STACK_MANIFEST,
252
+ detectGraphPaths,
253
+ syncGraphArtifacts,
254
+ runStackDoctor,
255
+ runStackSetup,
256
+ formatStackSummary,
257
+ tierWorkflows,
258
+ tierRouting
259
+ };
@@ -4,6 +4,10 @@ const path = require("path");
4
4
  const { URL } = require("url");
5
5
  const { routeTask, formatTextReport } = require("./route-engine");
6
6
  const { runDoctor, runStatus } = require("./doctor");
7
+ const { planRecover, applyRecover } = require("./recover");
8
+ const { enableSkill } = require("./disabled-skills");
9
+ const { appendFeedback } = require("./feedback");
10
+ const { resolveLocale, parseAcceptLanguage } = require("./i18n");
7
11
 
8
12
  const MIME = {
9
13
  ".html": "text/html; charset=utf-8",
@@ -42,8 +46,14 @@ function json(res, status, data) {
42
46
  res.end(body);
43
47
  }
44
48
 
45
- function projectHealth(rootDir) {
46
- const doctor = runDoctor(rootDir);
49
+ function resolveRequestLocale(req, body = {}) {
50
+ if (body.locale) return resolveLocale(body.locale);
51
+ return resolveLocale(parseAcceptLanguage(req.headers["accept-language"]));
52
+ }
53
+
54
+ function projectHealth(rootDir, locale) {
55
+ const loc = resolveLocale(locale);
56
+ const doctor = runDoctor(rootDir, { locale: loc });
47
57
  const status = runStatus(rootDir);
48
58
  return {
49
59
  ok: doctor.pass,
@@ -72,6 +82,9 @@ function buildChatCopy(result) {
72
82
  const opts = (ei.confirm_options || []).map((o) => o.id).join(" | ");
73
83
  lines.push(`Confirm: ${opts} (default: ${ei.confirm_default_option})`);
74
84
  }
85
+ if (ei.explanation?.reason_plain) {
86
+ lines.push(`Why: ${ei.explanation.reason_plain}`);
87
+ }
75
88
  if (ei.explanation?.files_to_read?.length) {
76
89
  lines.push("Read first:");
77
90
  for (const f of ei.explanation.files_to_read) lines.push(`- ${f}`);
@@ -79,13 +92,34 @@ function buildChatCopy(result) {
79
92
  return lines.join("\n");
80
93
  }
81
94
 
95
+ function applyDoctorAction(rootDir, actionId, doctor) {
96
+ const action = (doctor.actions || []).find((a) => a.id === actionId);
97
+ if (!action) return { error: `Unknown action: ${actionId}` };
98
+
99
+ if (action.id === "recover_latest") {
100
+ const plan = planRecover(rootDir, { latest: true });
101
+ if (plan.error) return plan;
102
+ return applyRecover(rootDir, plan);
103
+ }
104
+ if (action.id === "enable_core" && action.skill) {
105
+ return enableSkill(rootDir, action.skill);
106
+ }
107
+ return { error: "Action not supported via UI" };
108
+ }
109
+
82
110
  function createUiServer({ rootDir, uiDir = pkgUiDir(), port = 3721, host = "127.0.0.1" }) {
83
111
  const server = http.createServer(async (req, res) => {
84
112
  const url = new URL(req.url, `http://${host}:${port}`);
85
113
  const pathname = url.pathname;
114
+ const locale = resolveRequestLocale(req);
86
115
 
87
116
  if (req.method === "GET" && pathname === "/api/health") {
88
- return json(res, 200, projectHealth(rootDir));
117
+ return json(res, 200, projectHealth(rootDir, locale));
118
+ }
119
+
120
+ if (req.method === "GET" && pathname === "/api/locale") {
121
+ const { loadLocale } = require("./i18n");
122
+ return json(res, 200, { locale, strings: loadLocale(locale) });
89
123
  }
90
124
 
91
125
  if (req.method === "POST" && pathname === "/api/route") {
@@ -93,7 +127,16 @@ function createUiServer({ rootDir, uiDir = pkgUiDir(), port = 3721, host = "127.
93
127
  const body = await readBody(req);
94
128
  const task = String(body.task || "").trim();
95
129
  if (!task) return json(res, 400, { error: "task required" });
96
- const result = routeTask(task, rootDir);
130
+ const prevLocale = process.env.CAPINT_LOCALE;
131
+ const reqLocale = resolveRequestLocale(req, body);
132
+ process.env.CAPINT_LOCALE = reqLocale;
133
+ let result;
134
+ try {
135
+ result = routeTask(task, rootDir);
136
+ } finally {
137
+ if (prevLocale === undefined) delete process.env.CAPINT_LOCALE;
138
+ else process.env.CAPINT_LOCALE = prevLocale;
139
+ }
97
140
  if (result.error) return json(res, 400, { error: result.error });
98
141
  return json(res, 200, {
99
142
  result,
@@ -105,6 +148,44 @@ function createUiServer({ rootDir, uiDir = pkgUiDir(), port = 3721, host = "127.
105
148
  }
106
149
  }
107
150
 
151
+ if (req.method === "POST" && pathname === "/api/recover") {
152
+ try {
153
+ const body = await readBody(req);
154
+ const apply = Boolean(body.apply);
155
+ const actionId = body.action_id;
156
+
157
+ if (actionId) {
158
+ const doctor = runDoctor(rootDir, { locale: resolveRequestLocale(req, body) });
159
+ if (!apply) {
160
+ const action = (doctor.actions || []).find((a) => a.id === actionId);
161
+ return json(res, 200, { dry_run: true, action });
162
+ }
163
+ const result = applyDoctorAction(rootDir, actionId, doctor);
164
+ if (result.error) return json(res, 400, result);
165
+ return json(res, 200, { ok: true, applied: actionId, result, health: projectHealth(rootDir) });
166
+ }
167
+
168
+ const plan = planRecover(rootDir, { latest: body.latest !== false });
169
+ if (plan.error) return json(res, 400, plan);
170
+ if (!apply) return json(res, 200, { dry_run: true, ...plan });
171
+ const result = applyRecover(rootDir, plan);
172
+ return json(res, 200, { ok: true, result, health: projectHealth(rootDir) });
173
+ } catch (e) {
174
+ return json(res, 400, { error: e.message });
175
+ }
176
+ }
177
+
178
+ if (req.method === "POST" && pathname === "/api/feedback") {
179
+ try {
180
+ const body = await readBody(req);
181
+ const result = appendFeedback(rootDir, body);
182
+ if (result.error) return json(res, 400, result);
183
+ return json(res, 200, result);
184
+ } catch (e) {
185
+ return json(res, 400, { error: e.message });
186
+ }
187
+ }
188
+
108
189
  let filePath = pathname === "/" ? "/index.html" : pathname;
109
190
  filePath = path.normalize(filePath).replace(/^(\.\.[/\\])+/, "");
110
191
  const abs = path.join(uiDir, filePath);
@@ -136,4 +217,10 @@ function createUiServer({ rootDir, uiDir = pkgUiDir(), port = 3721, host = "127.
136
217
  };
137
218
  }
138
219
 
139
- module.exports = { createUiServer, projectHealth, buildChatCopy, pkgUiDir };
220
+ module.exports = {
221
+ createUiServer,
222
+ projectHealth,
223
+ buildChatCopy,
224
+ applyDoctorAction,
225
+ pkgUiDir
226
+ };
@@ -0,0 +1,10 @@
1
+ # DONE — {{project_name}}
2
+
3
+ > **Append-only** tamamlanan işler. Agent geniş kapsam önermeden önce buraya bakar.
4
+ > Aktif oturum: [HANDOFF.md](HANDOFF.md) (varsa)
5
+
6
+ ## Log
7
+
8
+ | Date | Summary | Verification |
9
+ |------|---------|--------------|
10
+ | {{date}} | CapInt init — proje hazır | `capint doctor` |
@@ -2,11 +2,25 @@
2
2
 
3
3
  > **Terminal zorunlu değil.** Kurulumdan sonra her şey chat’te.
4
4
 
5
+ ## CapInt stack (4 katman)
6
+
7
+ | Katman | Günlük |
8
+ |--------|--------|
9
+ | Workflows | `/forge` `/blueprint` — drift/regresyon kapısı |
10
+ | Routing | Bu dosya + chat (Execution Intent) |
11
+ | Graph | Cursor `/graphify …` (proje değişince `--update`) |
12
+ | Memory | agentmemory MCP + oturum kapanışında HANDOFF/DONE |
13
+
14
+ Kurulum: `capint init --stack` → `capint stack doctor` · [docs/capint-stack.md](docs/capint-stack.md)
15
+
16
+ ---
17
+
5
18
  1. Chat'e görevi doğal dille yaz (+ screenshot varsa ekle).
6
19
  2. Agent kısa **Execution Intent** gösterir (`Intent` / `Capability` / `Resolution` / `Plan`).
7
20
  3. **Hafif iş:** soru yok, doğrudan uygular. **Orta/ağır:** `devam` / `plan` / `analiz` yeterli.
8
21
  4. Skill veya workflow adı **ezberlemezsin**; seçimi agent yapar.
9
22
  5. `/workflow` veya `/skill` sadece override içindir (nadiren gerekir).
10
- 6. Terminal yalnızca: **ilk kurulum** (`init`), **sorun** (`doctor`), **IDE kuralı** (`ide sync`).
23
+ 6. Terminal yalnızca: **ilk kurulum** (`init --stack`), **stack doctor**, sorun (`doctor`).
24
+ 7. **Oturum kapanışı:** `kapan` veya `HANDOFF + DONE güncelle` — tamamlanan iş `DONE.md`, sıradaki iş `HANDOFF.md`.
11
25
 
12
26
  **Tam kılavuz:** [docs/kullanim-kilavuzu.md](docs/kullanim-kilavuzu.md) · **Chat şablonları:** [docs/conventions/daily-use.md](docs/conventions/daily-use.md)
@@ -16,7 +16,9 @@ Günlük iş **Cursor (veya IDE) chat’i** — terminal prosedürü değil.
16
16
  | Görevi normal dille yaz | `AGENT.md` + IDE kurallarını okur |
17
17
  | Screenshot / “beklenen vs mevcut” ekle | Kısa Execution Intent; orta/ağırda kısa soru |
18
18
  | “Önce plan, onaylayınca kod” de | Seçimine göre plan veya kod |
19
- | İstersen oturum sonu özet onayla | Değişiklik özeti sunar |
19
+ | İstersen oturum sonu özet onayla | Değişiklik özeti; `DONE.md` + `HANDOFF.md` günceller |
20
+
21
+ **Kapanış:** `kapan` veya `HANDOFF + DONE güncelle`
20
22
 
21
23
  **Yazmana gerek yok:** Skill adları, `capint route` çıktısı, matrix jargonu.
22
24
 
package/ui/app.js CHANGED
@@ -1,4 +1,6 @@
1
1
  let routePayload = null;
2
+ let healthPayload = null;
3
+ let selectedRating = null;
2
4
 
3
5
  function showStep(n) {
4
6
  document.querySelectorAll(".step-tab").forEach((el) => {
@@ -28,15 +30,32 @@ async function fetchHealth() {
28
30
  return res.json();
29
31
  }
30
32
 
33
+ function renderRecoverCard(data) {
34
+ const card = document.getElementById("recover-card");
35
+ const summary = document.getElementById("recover-summary");
36
+ const btn = document.getElementById("btn-recover");
37
+ const actions = data.doctor?.actions || [];
38
+ if (data.ok || !actions.length) {
39
+ card.hidden = true;
40
+ btn.dataset.actionId = "";
41
+ return;
42
+ }
43
+ card.hidden = false;
44
+ summary.textContent = data.doctor.summary_plain || data.doctor.hint;
45
+ btn.dataset.actionId = actions[0].id;
46
+ btn.textContent = actions[0].label || "Düzelt";
47
+ }
48
+
31
49
  function formatHealth(data) {
32
50
  const d = data.doctor;
33
51
  const s = data.status;
34
52
  const lines = [
35
- data.ok ? "✓ Doctor: OK" : "✗ Doctor: issues found",
53
+ data.ok ? "✓ Doctor: OK" : "✗ Doctor: sorun var",
36
54
  `Skills on disk: ${s.skills_on_disk}`,
37
55
  `Matrix: ${s.matrix_version || "—"}`,
38
56
  `CapInt pkg: ${s.capint_package || "—"}`
39
57
  ];
58
+ if (d.summary_plain) lines.push("", d.summary_plain);
40
59
  if (d.warnings?.length) {
41
60
  lines.push("", "Warnings:");
42
61
  for (const w of d.warnings.slice(0, 5)) lines.push(` • ${w}`);
@@ -49,20 +68,33 @@ function formatHealth(data) {
49
68
  return lines.join("\n");
50
69
  }
51
70
 
71
+ function escapeHtml(s) {
72
+ return String(s)
73
+ .replace(/&/g, "&")
74
+ .replace(/</g, "&lt;")
75
+ .replace(/>/g, "&gt;")
76
+ .replace(/"/g, "&quot;");
77
+ }
78
+
52
79
  function renderPlan(payload) {
53
80
  const ei = payload.result.execution_intent;
54
81
  const ex = ei.explanation || {};
55
82
  const card = document.getElementById("plan-card");
56
- const files = (ex.files_to_read || []).map((f) => `<li><code>${f}</code></li>`).join("");
83
+ const files = (ex.files_to_read || []).map((f) => `<li><code>${escapeHtml(f)}</code></li>`).join("");
84
+ const plain = ex.reason_plain ? `<p class="plain">${escapeHtml(ex.reason_plain)}</p>` : "";
85
+ const tip = ex.tooltip ? `<p class="hint"><span class="tip" title="${escapeHtml(ex.tooltip)}">ⓘ</span> ${escapeHtml(ex.tooltip)}</p>` : "";
86
+ const technical = ex.reason_summary
87
+ ? `<details><summary>Teknik detay</summary><p>${escapeHtml(ex.reason_summary)}</p></details>`
88
+ : "";
57
89
  const boundary = ei.workspace_boundary?.violated
58
- ? `<p class="error">Workspace sınırı: ${ei.workspace_boundary.override_hint}</p>`
90
+ ? `<p class="error">Workspace sınırı: ${escapeHtml(ei.workspace_boundary.override_hint || "")}</p>`
59
91
  : "";
60
92
  card.innerHTML = `
61
93
  <dl>
62
- <dt>Capability</dt><dd>${ei.capability}</dd>
63
- <dt>Resolution</dt><dd>${ei.resolution} (${ei.resolution_status || "installed"})</dd>
64
- <dt>Neden</dt><dd>${ex.reason_summary || "—"}</dd>
65
- <dt>Keywords</dt><dd>${(ex.matched_keywords || []).join(", ") || "—"}</dd>
94
+ <dt>Capability</dt><dd>${escapeHtml(ei.capability)}</dd>
95
+ <dt>Resolution</dt><dd>${escapeHtml(ei.resolution)} (${escapeHtml(ei.resolution_status || "installed")})</dd>
96
+ <dt>Neden</dt><dd>${plain}${tip}${technical}</dd>
97
+ <dt>Keywords</dt><dd>${escapeHtml((ex.matched_keywords || []).join(", ") || "—")}</dd>
66
98
  <dt>Okunacak dosyalar</dt><dd><ul>${files || "<li>—</li>"}</ul></dd>
67
99
  </dl>
68
100
  ${boundary}
@@ -86,14 +118,40 @@ document.getElementById("btn-health").addEventListener("click", async () => {
86
118
  out.textContent = "Kontrol ediliyor…";
87
119
  try {
88
120
  const data = await fetchHealth();
121
+ healthPayload = data;
89
122
  out.textContent = formatHealth(data);
90
- next.disabled = false;
123
+ renderRecoverCard(data);
124
+ next.disabled = !data.ok && (data.doctor?.actions || []).length > 0 ? false : !data.ok;
125
+ if (data.ok) next.disabled = false;
91
126
  } catch (e) {
92
127
  out.textContent = `Hata: ${e.message}`;
93
128
  next.disabled = true;
94
129
  }
95
130
  });
96
131
 
132
+ document.getElementById("btn-recover").addEventListener("click", async () => {
133
+ const actionId = document.getElementById("btn-recover").dataset.actionId;
134
+ if (!actionId) return;
135
+ if (!window.confirm("Önerilen düzeltmeyi uygulamak istiyor musun?")) return;
136
+ const out = document.getElementById("health-out");
137
+ out.textContent = "Düzeltiliyor…";
138
+ try {
139
+ const res = await fetch("/api/recover", {
140
+ method: "POST",
141
+ headers: { "Content-Type": "application/json" },
142
+ body: JSON.stringify({ apply: true, action_id: actionId })
143
+ });
144
+ const data = await res.json();
145
+ if (!res.ok) throw new Error(data.error || "Recover failed");
146
+ healthPayload = data.health;
147
+ out.textContent = formatHealth(data.health);
148
+ renderRecoverCard(data.health);
149
+ document.getElementById("to-step-2").disabled = !data.health.ok;
150
+ } catch (e) {
151
+ out.textContent = `Hata: ${e.message}`;
152
+ }
153
+ });
154
+
97
155
  document.getElementById("to-step-2").addEventListener("click", () => showStep(2));
98
156
 
99
157
  document.getElementById("btn-route").addEventListener("click", async () => {
@@ -111,8 +169,8 @@ document.getElementById("btn-route").addEventListener("click", async () => {
111
169
  try {
112
170
  const res = await fetch("/api/route", {
113
171
  method: "POST",
114
- headers: { "Content-Type": "application/json" },
115
- body: JSON.stringify({ task })
172
+ headers: { "Content-Type": "application/json", "Accept-Language": "tr" },
173
+ body: JSON.stringify({ task, locale: "tr" })
116
174
  });
117
175
  const data = await res.json();
118
176
  if (!res.ok) throw new Error(data.error || "Route failed");
@@ -158,10 +216,42 @@ document.getElementById("btn-copy").addEventListener("click", async () => {
158
216
 
159
217
  document.getElementById("btn-restart").addEventListener("click", () => {
160
218
  routePayload = null;
219
+ selectedRating = null;
161
220
  document.getElementById("task-input").value = "";
162
221
  document.getElementById("chat-copy").value = "";
163
222
  document.getElementById("copy-status").textContent = "";
223
+ document.getElementById("feedback-status").textContent = "";
224
+ document.getElementById("feedback-comment").value = "";
225
+ document.querySelectorAll("#feedback-stars button").forEach((b) => b.classList.remove("active"));
164
226
  showStep(2);
165
227
  });
166
228
 
229
+ document.querySelectorAll("#feedback-stars button").forEach((btn) => {
230
+ btn.addEventListener("click", () => {
231
+ selectedRating = Number(btn.dataset.rating);
232
+ document.querySelectorAll("#feedback-stars button").forEach((b) => b.classList.remove("active"));
233
+ btn.classList.add("active");
234
+ });
235
+ });
236
+
237
+ document.getElementById("btn-feedback").addEventListener("click", async () => {
238
+ const status = document.getElementById("feedback-status");
239
+ try {
240
+ const res = await fetch("/api/feedback", {
241
+ method: "POST",
242
+ headers: { "Content-Type": "application/json" },
243
+ body: JSON.stringify({
244
+ rating: selectedRating,
245
+ comment: document.getElementById("feedback-comment").value,
246
+ source: "ui"
247
+ })
248
+ });
249
+ const data = await res.json();
250
+ if (!res.ok) throw new Error(data.error || "Feedback failed");
251
+ status.textContent = "Teşekkürler — geri bildirim kaydedildi (yerel).";
252
+ } catch (e) {
253
+ status.textContent = e.message;
254
+ }
255
+ });
256
+
167
257
  showStep(1);
package/ui/index.html CHANGED
@@ -11,6 +11,9 @@
11
11
  <header class="header">
12
12
  <h1>CapInt</h1>
13
13
  <p class="subtitle">Terminal yok — görevini yaz, planı gör, chat'e kopyala.</p>
14
+ <p class="hint">
15
+ <a href="https://github.com/bcelep/capint/blob/main/docs/kullanim-kilavuzu.md" target="_blank" rel="noopener">SSS / kullanım kılavuzu</a>
16
+ </p>
14
17
  </header>
15
18
 
16
19
  <nav class="steps" aria-label="Adımlar">
@@ -23,11 +26,16 @@
23
26
  <section class="panel active" data-panel="1">
24
27
  <h2>Proje hazır mı?</h2>
25
28
  <p class="hint">
26
- <span class="tip" title="installed = skills/ klasöründe SKILL.md var. Cursor skill listesinde görünmesi gerekmez.">ⓘ</span>
29
+ <span class="tip" id="tip-installed" title="installed = skills/ klasöründe SKILL.md var. Cursor skill listesinde görünmesi gerekmez.">ⓘ</span>
27
30
  Skill'ler diskte yüklü mü? Doctor kontrol eder.
28
31
  </p>
29
32
  <button type="button" class="btn primary" id="btn-health">Kontrol et</button>
30
33
  <pre class="output" id="health-out" aria-live="polite"></pre>
34
+ <div id="recover-card" class="card recover-card" hidden>
35
+ <h3>Önerilen düzeltme</h3>
36
+ <p id="recover-summary"></p>
37
+ <button type="button" class="btn primary" id="btn-recover">Düzelt</button>
38
+ </div>
31
39
  <div class="actions">
32
40
  <button type="button" class="btn" id="to-step-2" disabled>Devam →</button>
33
41
  </div>
@@ -68,6 +76,19 @@
68
76
  <button type="button" class="btn" id="btn-restart">Yeni görev</button>
69
77
  </div>
70
78
  <p class="copy-status" id="copy-status" aria-live="polite"></p>
79
+ <fieldset class="feedback-field">
80
+ <legend>Bu akış işine yaradı mı?</legend>
81
+ <div class="stars" id="feedback-stars">
82
+ <button type="button" data-rating="1">1</button>
83
+ <button type="button" data-rating="2">2</button>
84
+ <button type="button" data-rating="3">3</button>
85
+ <button type="button" data-rating="4">4</button>
86
+ <button type="button" data-rating="5">5</button>
87
+ </div>
88
+ <textarea id="feedback-comment" rows="2" placeholder="İsteğe bağlı yorum"></textarea>
89
+ <button type="button" class="btn" id="btn-feedback">Geri bildirim gönder</button>
90
+ <p id="feedback-status" class="copy-status" aria-live="polite"></p>
91
+ </fieldset>
71
92
  </section>
72
93
  </main>
73
94
  <script src="/app.js"></script>