@bcelep/capint 0.4.2
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/AGENT.md +28 -0
- package/CHANGELOG.md +58 -0
- package/README.md +94 -0
- package/bin/capint.js +90 -0
- package/design.md +23 -0
- package/docs/architecture-decisions.md +95 -0
- package/docs/execution-intent-contract.md +81 -0
- package/docs/manifest-schema.md +36 -0
- package/docs/release-checklist.md +31 -0
- package/package.json +33 -0
- package/projections/session-start.md +32 -0
- package/registry.json +12 -0
- package/scripts/release-check.mjs +40 -0
- package/scripts/validate-matrix.mjs +83 -0
- package/skill-routing-matrix.json +150 -0
- package/skills/capability-router/SKILL.md +3 -0
- package/skills/context-memory-bridge/SKILL.md +17 -0
- package/skills/localization-hub/SKILL.md +17 -0
- package/skills/refactor/SKILL.md +17 -0
- package/skills/systematic-debugging/SKILL.md +19 -0
- package/src/commands/audit.js +32 -0
- package/src/commands/consult.js +64 -0
- package/src/commands/doctor.js +36 -0
- package/src/commands/ide.js +62 -0
- package/src/commands/init.js +50 -0
- package/src/commands/memory.js +60 -0
- package/src/commands/route.js +30 -0
- package/src/commands/scaffold.js +37 -0
- package/src/commands/status.js +22 -0
- package/src/commands/uninstall.js +46 -0
- package/src/commands/upgrade.js +69 -0
- package/src/lib/audit.js +107 -0
- package/src/lib/capability-router.js +118 -0
- package/src/lib/context-memory-bridge.js +87 -0
- package/src/lib/context-pack.js +39 -0
- package/src/lib/contract.js +71 -0
- package/src/lib/doctor.js +115 -0
- package/src/lib/event-log.js +40 -0
- package/src/lib/execution-policy.js +168 -0
- package/src/lib/ide-sync.js +277 -0
- package/src/lib/intent-parser.js +116 -0
- package/src/lib/orchestration.js +25 -0
- package/src/lib/providers/activation-policy.js +93 -0
- package/src/lib/providers/graph-provider.js +35 -0
- package/src/lib/providers/local-graph-adapter.js +40 -0
- package/src/lib/providers/local-memory-adapter.js +41 -0
- package/src/lib/providers/memory-provider.js +35 -0
- package/src/lib/route-engine.js +191 -0
- package/src/lib/scaffold/file-policy.js +92 -0
- package/src/lib/scaffold/index.js +29 -0
- package/src/lib/scaffold/manifest-schema.js +116 -0
- package/src/lib/scaffold/manifest.js +34 -0
- package/src/lib/scaffold/presets.js +132 -0
- package/src/lib/uninstall.js +126 -0
- package/src/lib/upgrade-matrix.js +120 -0
- package/templates/bundle/skills/capability-router/SKILL.md +12 -0
- package/templates/bundle/skills/context-memory-bridge/SKILL.md +17 -0
- package/templates/bundle/skills/localization-hub/SKILL.md +17 -0
- package/templates/bundle/skills/refactor/SKILL.md +17 -0
- package/templates/bundle/skills/systematic-debugging/SKILL.md +19 -0
- package/templates/bundle/workflows/forge.md +51 -0
- package/templates/minimal/.capint/rules/core.md +18 -0
- package/templates/minimal/AGENT.md +26 -0
- package/templates/minimal/AGENTS.md +9 -0
- package/templates/minimal/design.md +25 -0
- package/templates/minimal/registry.json +7 -0
- package/templates/prismx-compatible/.capint/context.json +8 -0
- package/templates/prismx-compatible/HANDOFF.md +19 -0
- package/workflows/forge.md +51 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { applyFilePolicy, buildReport } = require("./file-policy");
|
|
2
|
+
const { getPresetFiles, PRESET_MANIFEST, copyBundle } = require("./presets");
|
|
3
|
+
const { writeScaffoldManifest } = require("./manifest");
|
|
4
|
+
|
|
5
|
+
function runScaffold({ rootDir, preset = "minimal", projectName = "project", force = false }) {
|
|
6
|
+
if (!PRESET_MANIFEST[preset]) {
|
|
7
|
+
return { error: `unknown preset: ${preset}. Available: ${Object.keys(PRESET_MANIFEST).join(", ")}` };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const files = getPresetFiles(preset, {
|
|
11
|
+
project_name: projectName,
|
|
12
|
+
date: new Date().toISOString().slice(0, 10)
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const results = [];
|
|
16
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
17
|
+
results.push(applyFilePolicy({ rootDir, relativePath: rel, content, force }));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
results.push(...copyBundle({ rootDir, force }));
|
|
21
|
+
|
|
22
|
+
const report = buildReport(results);
|
|
23
|
+
report.preset = preset;
|
|
24
|
+
report.manifest = PRESET_MANIFEST[preset];
|
|
25
|
+
report.manifest_path = writeScaffoldManifest(rootDir, report);
|
|
26
|
+
return report;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { runScaffold, PRESET_MANIFEST };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const MANIFEST_SCHEMA_VERSION = "2.0";
|
|
2
|
+
|
|
3
|
+
const ACTIONS = new Set(["created", "updated", "skipped", "conflict"]);
|
|
4
|
+
const SOURCES = new Set(["capint", "user"]);
|
|
5
|
+
const KINDS = new Set(["doc", "skill", "workflow", "matrix", "registry", "ide_projection"]);
|
|
6
|
+
|
|
7
|
+
function inferKindFromPath(relPath) {
|
|
8
|
+
const p = relPath.replace(/\\/g, "/");
|
|
9
|
+
if (p.startsWith("skills/")) return "skill";
|
|
10
|
+
if (p.startsWith("workflows/")) return "workflow";
|
|
11
|
+
if (p === "skill-routing-matrix.json") return "matrix";
|
|
12
|
+
if (p === "registry.json") return "registry";
|
|
13
|
+
if (
|
|
14
|
+
p.includes(".cursor/rules/") ||
|
|
15
|
+
p === "CLAUDE.md" ||
|
|
16
|
+
p === "GEMINI.md" ||
|
|
17
|
+
p.startsWith(".agents/")
|
|
18
|
+
) {
|
|
19
|
+
return "ide_projection";
|
|
20
|
+
}
|
|
21
|
+
return "doc";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function defaultFileMeta(relPath, overrides = {}) {
|
|
25
|
+
const kind = overrides.kind || inferKindFromPath(relPath);
|
|
26
|
+
const source = overrides.source || "capint";
|
|
27
|
+
const removable =
|
|
28
|
+
typeof overrides.removable === "boolean"
|
|
29
|
+
? overrides.removable
|
|
30
|
+
: source === "capint" && kind !== "doc";
|
|
31
|
+
return {
|
|
32
|
+
path: relPath.replace(/\\/g, "/"),
|
|
33
|
+
source,
|
|
34
|
+
removable,
|
|
35
|
+
kind,
|
|
36
|
+
marker: overrides.marker ?? null
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function enrichFileEntry(entry) {
|
|
41
|
+
const pathNorm = (entry.path || "").replace(/\\/g, "/");
|
|
42
|
+
const meta = defaultFileMeta(pathNorm, {
|
|
43
|
+
source: entry.source,
|
|
44
|
+
removable: entry.removable,
|
|
45
|
+
kind: entry.kind,
|
|
46
|
+
marker: entry.marker
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
path: pathNorm,
|
|
50
|
+
action: entry.action,
|
|
51
|
+
source: meta.source,
|
|
52
|
+
removable: meta.removable,
|
|
53
|
+
kind: meta.kind,
|
|
54
|
+
marker: meta.marker,
|
|
55
|
+
sidecar: entry.sidecar || null
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateManifest(manifest) {
|
|
60
|
+
const errors = [];
|
|
61
|
+
if (!manifest || typeof manifest !== "object") {
|
|
62
|
+
return { valid: false, errors: ["manifest must be an object"] };
|
|
63
|
+
}
|
|
64
|
+
if (manifest.schema_version !== MANIFEST_SCHEMA_VERSION) {
|
|
65
|
+
errors.push(`schema_version must be ${MANIFEST_SCHEMA_VERSION}`);
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(manifest.files)) {
|
|
68
|
+
errors.push("files must be an array");
|
|
69
|
+
return { valid: false, errors };
|
|
70
|
+
}
|
|
71
|
+
for (let i = 0; i < manifest.files.length; i++) {
|
|
72
|
+
const f = manifest.files[i];
|
|
73
|
+
if (!f.path || typeof f.path !== "string") errors.push(`files[${i}].path required`);
|
|
74
|
+
if (!ACTIONS.has(f.action)) errors.push(`files[${i}].action invalid: ${f.action}`);
|
|
75
|
+
if (!SOURCES.has(f.source)) errors.push(`files[${i}].source invalid: ${f.source}`);
|
|
76
|
+
if (typeof f.removable !== "boolean") errors.push(`files[${i}].removable must be boolean`);
|
|
77
|
+
if (!KINDS.has(f.kind)) errors.push(`files[${i}].kind invalid: ${f.kind}`);
|
|
78
|
+
}
|
|
79
|
+
return { valid: errors.length === 0, errors };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildManifestDocument({ report, previous = null }) {
|
|
83
|
+
const files = (report.results || []).map(enrichFileEntry);
|
|
84
|
+
const manifest = {
|
|
85
|
+
schema_version: MANIFEST_SCHEMA_VERSION,
|
|
86
|
+
last_run: report.generated_at,
|
|
87
|
+
preset: report.preset,
|
|
88
|
+
summary: report.summary,
|
|
89
|
+
files,
|
|
90
|
+
history: [
|
|
91
|
+
...(previous?.history || []).slice(-9),
|
|
92
|
+
{
|
|
93
|
+
at: report.generated_at,
|
|
94
|
+
preset: report.preset,
|
|
95
|
+
summary: report.summary
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
const validation = validateManifest(manifest);
|
|
100
|
+
if (!validation.valid) {
|
|
101
|
+
throw new Error(`manifest validation failed: ${validation.errors.join("; ")}`);
|
|
102
|
+
}
|
|
103
|
+
return manifest;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
MANIFEST_SCHEMA_VERSION,
|
|
108
|
+
ACTIONS,
|
|
109
|
+
SOURCES,
|
|
110
|
+
KINDS,
|
|
111
|
+
inferKindFromPath,
|
|
112
|
+
defaultFileMeta,
|
|
113
|
+
enrichFileEntry,
|
|
114
|
+
validateManifest,
|
|
115
|
+
buildManifestDocument
|
|
116
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { buildManifestDocument } = require("./manifest-schema");
|
|
4
|
+
|
|
5
|
+
function writeScaffoldManifest(rootDir, report) {
|
|
6
|
+
const capintDir = path.join(rootDir, ".capint");
|
|
7
|
+
if (!fs.existsSync(capintDir)) fs.mkdirSync(capintDir, { recursive: true });
|
|
8
|
+
|
|
9
|
+
const manifestPath = path.join(capintDir, "scaffold-manifest.json");
|
|
10
|
+
let previous = null;
|
|
11
|
+
if (fs.existsSync(manifestPath)) {
|
|
12
|
+
try {
|
|
13
|
+
previous = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
14
|
+
} catch {
|
|
15
|
+
previous = null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const manifest = buildManifestDocument({ report, previous });
|
|
20
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
|
|
21
|
+
return manifestPath;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readScaffoldManifest(rootDir) {
|
|
25
|
+
const manifestPath = path.join(rootDir, ".capint", "scaffold-manifest.json");
|
|
26
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { writeScaffoldManifest, readScaffoldManifest };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { applyFilePolicy } = require("./file-policy");
|
|
4
|
+
|
|
5
|
+
function loadTemplate(name) {
|
|
6
|
+
const tplDir = path.join(__dirname, "..", "..", "..", "templates", name);
|
|
7
|
+
const files = {};
|
|
8
|
+
if (!fs.existsSync(tplDir)) return files;
|
|
9
|
+
for (const rel of walk(tplDir)) {
|
|
10
|
+
const key = rel.replace(/\\/g, "/");
|
|
11
|
+
files[key] = fs.readFileSync(path.join(tplDir, rel), "utf-8");
|
|
12
|
+
}
|
|
13
|
+
return files;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function walk(dir, base = "") {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
19
|
+
const rel = base ? `${base}/${ent.name}` : ent.name;
|
|
20
|
+
if (ent.isDirectory()) out.push(...walk(path.join(dir, ent.name), rel));
|
|
21
|
+
else out.push(rel);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function interpolate(content, vars) {
|
|
27
|
+
let out = content;
|
|
28
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
29
|
+
out = out.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function deriveRegistryFromMatrix(matrix) {
|
|
35
|
+
const core = new Set(["capability-router"]);
|
|
36
|
+
const recommended = new Set();
|
|
37
|
+
|
|
38
|
+
for (const tt of matrix?.task_types || []) {
|
|
39
|
+
for (const s of tt.skills || []) {
|
|
40
|
+
if (s === "capability-router") core.add(s);
|
|
41
|
+
else recommended.add(s);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const meta of Object.values(matrix?.rules_domain_map || {})) {
|
|
45
|
+
for (const s of meta.skills || []) {
|
|
46
|
+
if (s === "capability-router") core.add(s);
|
|
47
|
+
else recommended.add(s);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
recommended.delete("capability-router");
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
schema_version: "1.0",
|
|
54
|
+
core: [...core].sort(),
|
|
55
|
+
recommended: [...recommended].sort(),
|
|
56
|
+
optional: [],
|
|
57
|
+
project_added: []
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getPresetFiles(preset, vars = {}) {
|
|
62
|
+
const pkgRoot = path.join(__dirname, "..", "..", "..");
|
|
63
|
+
const matrixRaw = fs.readFileSync(path.join(pkgRoot, "skill-routing-matrix.json"), "utf-8");
|
|
64
|
+
const matrix = JSON.parse(matrixRaw);
|
|
65
|
+
const registryJson = `${JSON.stringify(deriveRegistryFromMatrix(matrix), null, 2)}\n`;
|
|
66
|
+
const routingBundle = {
|
|
67
|
+
"skill-routing-matrix.json": matrixRaw,
|
|
68
|
+
"registry.json": registryJson
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const presets = {
|
|
72
|
+
minimal: () => ({ ...loadTemplate("minimal"), ...routingBundle }),
|
|
73
|
+
"prismx-compatible": () => {
|
|
74
|
+
const minimal = loadTemplate("minimal");
|
|
75
|
+
const extra = loadTemplate("prismx-compatible");
|
|
76
|
+
return { ...minimal, ...extra, ...routingBundle };
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const loader = presets[preset] || presets.minimal;
|
|
80
|
+
const raw = loader();
|
|
81
|
+
const files = {};
|
|
82
|
+
for (const [rel, content] of Object.entries(raw)) {
|
|
83
|
+
files[rel] = interpolate(content, vars);
|
|
84
|
+
}
|
|
85
|
+
return files;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function copyBundle({ rootDir, force = false }) {
|
|
89
|
+
const bundleDir = path.join(__dirname, "..", "..", "..", "templates", "bundle");
|
|
90
|
+
const results = [];
|
|
91
|
+
if (!fs.existsSync(bundleDir)) return results;
|
|
92
|
+
|
|
93
|
+
for (const rel of walk(bundleDir)) {
|
|
94
|
+
const relNorm = rel.replace(/\\/g, "/");
|
|
95
|
+
const content = fs.readFileSync(path.join(bundleDir, rel), "utf-8");
|
|
96
|
+
results.push(
|
|
97
|
+
applyFilePolicy({
|
|
98
|
+
rootDir,
|
|
99
|
+
relativePath: relNorm,
|
|
100
|
+
content,
|
|
101
|
+
force
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const PRESET_MANIFEST = {
|
|
109
|
+
minimal: {
|
|
110
|
+
id: "minimal",
|
|
111
|
+
description: "AGENT.md, design.md, rules, matrix, registry, skill bundle",
|
|
112
|
+
files: [
|
|
113
|
+
"AGENT.md",
|
|
114
|
+
"AGENTS.md",
|
|
115
|
+
"design.md",
|
|
116
|
+
".capint/rules/core.md",
|
|
117
|
+
".gitignore",
|
|
118
|
+
"skill-routing-matrix.json",
|
|
119
|
+
"registry.json",
|
|
120
|
+
"skills/",
|
|
121
|
+
"workflows/"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
"prismx-compatible": {
|
|
125
|
+
id: "prismx-compatible",
|
|
126
|
+
description: "minimal + HANDOFF.md + .capint/context.json",
|
|
127
|
+
extends: "minimal",
|
|
128
|
+
files: ["HANDOFF.md", ".capint/context.json"]
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
module.exports = { getPresetFiles, PRESET_MANIFEST, interpolate, deriveRegistryFromMatrix, copyBundle };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { CAPINT_SYNC_MARKER } = require("./ide-sync");
|
|
4
|
+
const { readScaffoldManifest } = require("./scaffold/manifest");
|
|
5
|
+
|
|
6
|
+
function isCapintMarked(filePath) {
|
|
7
|
+
if (!fs.existsSync(filePath)) return false;
|
|
8
|
+
try {
|
|
9
|
+
return fs.readFileSync(filePath, "utf-8").includes(CAPINT_SYNC_MARKER);
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function collectManifestRemovals(rootDir, manifest, options = {}) {
|
|
16
|
+
const plan = [];
|
|
17
|
+
const { keepAgent = false, keepIde = false, includeSidecars = false } = options;
|
|
18
|
+
|
|
19
|
+
for (const entry of manifest?.files || []) {
|
|
20
|
+
if (entry.source !== "capint" || !entry.removable) continue;
|
|
21
|
+
if (entry.kind === "ide_projection" && keepIde) continue;
|
|
22
|
+
if (entry.path === "AGENT.md" && keepAgent) continue;
|
|
23
|
+
if (entry.path === "design.md" && keepAgent) continue;
|
|
24
|
+
if (entry.path === "AGENTS.md" && keepAgent) continue;
|
|
25
|
+
|
|
26
|
+
const abs = path.join(rootDir, entry.path);
|
|
27
|
+
if (entry.kind === "ide_projection" && !isCapintMarked(abs)) continue;
|
|
28
|
+
if (fs.existsSync(abs)) {
|
|
29
|
+
plan.push({ path: entry.path, abs, type: "file", kind: entry.kind });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (includeSidecars) {
|
|
34
|
+
for (const entry of manifest?.files || []) {
|
|
35
|
+
if (entry.sidecar) {
|
|
36
|
+
const abs = path.join(rootDir, entry.sidecar);
|
|
37
|
+
if (fs.existsSync(abs)) plan.push({ path: entry.sidecar, abs, type: "sidecar" });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const ent of fs.readdirSync(rootDir)) {
|
|
41
|
+
if (ent.endsWith(".capint.new.md")) {
|
|
42
|
+
plan.push({ path: ent, abs: path.join(rootDir, ent), type: "sidecar" });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const capintDir = path.join(rootDir, ".capint");
|
|
48
|
+
if (fs.existsSync(capintDir)) {
|
|
49
|
+
plan.push({ path: ".capint/", abs: capintDir, type: "dir" });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
return plan.filter((p) => {
|
|
54
|
+
if (seen.has(p.abs)) return false;
|
|
55
|
+
seen.add(p.abs);
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function collectIdeFallbackRemovals(rootDir, keepIde) {
|
|
61
|
+
if (keepIde) return [];
|
|
62
|
+
const candidates = [
|
|
63
|
+
path.join(rootDir, ".cursor", "rules", "00-capint-session.mdc"),
|
|
64
|
+
path.join(rootDir, "CLAUDE.md"),
|
|
65
|
+
path.join(rootDir, "GEMINI.md"),
|
|
66
|
+
path.join(rootDir, ".agents", "rules", "00-capint-session.md"),
|
|
67
|
+
path.join(rootDir, ".agents", "CAPINT.md"),
|
|
68
|
+
path.join(rootDir, ".agents", "skills", "capint-router", "SKILL.md")
|
|
69
|
+
];
|
|
70
|
+
return candidates
|
|
71
|
+
.filter((abs) => isCapintMarked(abs))
|
|
72
|
+
.map((abs) => ({
|
|
73
|
+
path: path.relative(rootDir, abs).replace(/\\/g, "/"),
|
|
74
|
+
abs,
|
|
75
|
+
type: "file",
|
|
76
|
+
kind: "ide_projection"
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildUninstallPlan(rootDir, options = {}) {
|
|
81
|
+
const manifest = readScaffoldManifest(rootDir);
|
|
82
|
+
const fromManifest = manifest
|
|
83
|
+
? collectManifestRemovals(rootDir, manifest, options)
|
|
84
|
+
: [];
|
|
85
|
+
const fromIde = collectIdeFallbackRemovals(rootDir, options.keepIde);
|
|
86
|
+
const merged = [...fromManifest, ...fromIde];
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
return merged.filter((p) => {
|
|
89
|
+
if (seen.has(p.abs)) return false;
|
|
90
|
+
seen.add(p.abs);
|
|
91
|
+
return true;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function removePath(entry) {
|
|
96
|
+
if (entry.type === "dir") {
|
|
97
|
+
fs.rmSync(entry.abs, { recursive: true, force: true });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
fs.rmSync(entry.abs, { force: true });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function executeUninstall(rootDir, plan, dryRun) {
|
|
104
|
+
const removed = [];
|
|
105
|
+
const skipped = [];
|
|
106
|
+
for (const entry of plan) {
|
|
107
|
+
if (!fs.existsSync(entry.abs)) {
|
|
108
|
+
skipped.push(entry.path);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (entry.kind === "ide_projection" && !isCapintMarked(entry.abs)) {
|
|
112
|
+
skipped.push(entry.path);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!dryRun) removePath(entry);
|
|
116
|
+
removed.push(entry.path);
|
|
117
|
+
}
|
|
118
|
+
return { removed, skipped };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
buildUninstallPlan,
|
|
123
|
+
executeUninstall,
|
|
124
|
+
isCapintMarked,
|
|
125
|
+
collectManifestRemovals
|
|
126
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
function loadJson(filePath) {
|
|
5
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function taskTypeIds(matrix) {
|
|
9
|
+
return new Set((matrix?.task_types || []).map((t) => t.id));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function mergeTaskTypes(packageMatrix, projectMatrix) {
|
|
13
|
+
const conflicts = [];
|
|
14
|
+
const customPreserved = [];
|
|
15
|
+
const pkgById = new Map((packageMatrix.task_types || []).map((t) => [t.id, t]));
|
|
16
|
+
const projById = new Map((projectMatrix.task_types || []).map((t) => [t.id, t]));
|
|
17
|
+
|
|
18
|
+
for (const [id, proj] of projById) {
|
|
19
|
+
if (!pkgById.has(id)) {
|
|
20
|
+
customPreserved.push(id);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const merged = [];
|
|
25
|
+
for (const pkgTt of packageMatrix.task_types || []) {
|
|
26
|
+
const projTt = projById.get(pkgTt.id);
|
|
27
|
+
if (!projTt) {
|
|
28
|
+
merged.push(structuredClone(pkgTt));
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const out = structuredClone(pkgTt);
|
|
32
|
+
for (const key of Object.keys(projTt)) {
|
|
33
|
+
if (key === "id") continue;
|
|
34
|
+
const pv = projTt[key];
|
|
35
|
+
const pkgv = pkgTt[key];
|
|
36
|
+
if (JSON.stringify(pv) !== JSON.stringify(pkgv)) {
|
|
37
|
+
conflicts.push({
|
|
38
|
+
id: pkgTt.id,
|
|
39
|
+
field: key,
|
|
40
|
+
project_value: pv,
|
|
41
|
+
package_value: pkgv,
|
|
42
|
+
resolution: "package_wins"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
merged.push(out);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const id of customPreserved) {
|
|
50
|
+
merged.push(structuredClone(projById.get(id)));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { merged, conflicts, custom_preserved: customPreserved };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function mergeMatrices(packageMatrix, projectMatrix) {
|
|
57
|
+
const topKeys = new Set([...Object.keys(packageMatrix), ...Object.keys(projectMatrix)]);
|
|
58
|
+
const conflicts = [];
|
|
59
|
+
const result = structuredClone(packageMatrix);
|
|
60
|
+
|
|
61
|
+
for (const key of topKeys) {
|
|
62
|
+
if (key === "task_types") continue;
|
|
63
|
+
if (!(key in projectMatrix)) continue;
|
|
64
|
+
if (JSON.stringify(projectMatrix[key]) !== JSON.stringify(packageMatrix[key])) {
|
|
65
|
+
conflicts.push({
|
|
66
|
+
id: "_top_level",
|
|
67
|
+
field: key,
|
|
68
|
+
project_value: projectMatrix[key],
|
|
69
|
+
package_value: packageMatrix[key],
|
|
70
|
+
resolution: "package_wins"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const tt = mergeTaskTypes(packageMatrix, projectMatrix);
|
|
76
|
+
result.task_types = tt.merged;
|
|
77
|
+
return {
|
|
78
|
+
merged: result,
|
|
79
|
+
conflicts: [...conflicts, ...tt.conflicts],
|
|
80
|
+
custom_preserved: tt.custom_preserved
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function planUpgrade(rootDir, packageRoot) {
|
|
85
|
+
const projectPath = path.join(rootDir, "skill-routing-matrix.json");
|
|
86
|
+
const packagePath = path.join(packageRoot, "skill-routing-matrix.json");
|
|
87
|
+
|
|
88
|
+
if (!fs.existsSync(projectPath)) {
|
|
89
|
+
return { error: "skill-routing-matrix.json not found in project" };
|
|
90
|
+
}
|
|
91
|
+
if (!fs.existsSync(packagePath)) {
|
|
92
|
+
return { error: "package skill-routing-matrix.json not found" };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const projectMatrix = loadJson(projectPath);
|
|
96
|
+
const packageMatrix = loadJson(packagePath);
|
|
97
|
+
const { merged, conflicts, custom_preserved } = mergeMatrices(packageMatrix, projectMatrix);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
project_schema: projectMatrix.schema_version,
|
|
101
|
+
package_schema: packageMatrix.schema_version,
|
|
102
|
+
conflicts,
|
|
103
|
+
custom_preserved,
|
|
104
|
+
merged,
|
|
105
|
+
summary: `${conflicts.length} conflicts — package wins; review before --apply`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function applyUpgrade(rootDir, merged) {
|
|
110
|
+
const projectPath = path.join(rootDir, "skill-routing-matrix.json");
|
|
111
|
+
const backupDir = path.join(rootDir, ".capint", "backups");
|
|
112
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
113
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
114
|
+
const backupPath = path.join(backupDir, `skill-routing-matrix.${stamp}.json`);
|
|
115
|
+
fs.copyFileSync(projectPath, backupPath);
|
|
116
|
+
fs.writeFileSync(projectPath, `${JSON.stringify(merged, null, 2)}\n`, "utf-8");
|
|
117
|
+
return backupPath;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { planUpgrade, applyUpgrade, mergeMatrices };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# capability-router
|
|
2
|
+
|
|
3
|
+
Routes normalized intent to capabilities/providers using `skill-routing-matrix.json`.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
- Ambiguous requests with no clear task type
|
|
8
|
+
- Fallback when no keyword match
|
|
9
|
+
|
|
10
|
+
## CapInt
|
|
11
|
+
|
|
12
|
+
Default fallback skill · CLI: `capint route "<task>"`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# context-memory-bridge
|
|
2
|
+
|
|
3
|
+
Use when the task requires prior decisions, handoff context, or session history lookup.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
- memory lookup, what did we decide, geçen karar, history
|
|
8
|
+
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
1. Read HANDOFF.md, AGENT.md, design.md when present
|
|
12
|
+
2. Summarize relevant prior decisions
|
|
13
|
+
3. Do not invent history; cite file paths
|
|
14
|
+
|
|
15
|
+
## CapInt
|
|
16
|
+
|
|
17
|
+
Matrix capability: `memory-retrieval` · Memory: required
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# localization-hub
|
|
2
|
+
|
|
3
|
+
Use for i18n, translation files, locale keys, and language hub tasks.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
- User mentions i18n, localization, çeviri, lang file, translation hub
|
|
8
|
+
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
1. Identify target locales and key namespaces
|
|
12
|
+
2. Check existing translation patterns in the repo
|
|
13
|
+
3. Apply consistent key naming; avoid hardcoded strings
|
|
14
|
+
|
|
15
|
+
## CapInt
|
|
16
|
+
|
|
17
|
+
Matrix capability: `localization-hub`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# refactor
|
|
2
|
+
|
|
3
|
+
Use when simplifying code, reducing smell, or cleaning structure without changing behavior.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
- refactor, clean up, simplify, code smell
|
|
8
|
+
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
1. Ensure tests exist or add minimal coverage
|
|
12
|
+
2. Make smallest structural change
|
|
13
|
+
3. Re-run tests; no behavior change unless intended
|
|
14
|
+
|
|
15
|
+
## CapInt
|
|
16
|
+
|
|
17
|
+
Matrix capability: `refactor-simplify` · Workflow: `/forge`
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# systematic-debugging
|
|
2
|
+
|
|
3
|
+
Use when fixing bugs, errors, or unexpected behavior before proposing fixes.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
- User reports broken behavior, test failures, or regressions
|
|
8
|
+
- Keywords: bug, error, fix, hata, çalışmıyor
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. Reproduce the issue with minimal steps
|
|
13
|
+
2. Gather evidence (logs, stack traces)
|
|
14
|
+
3. Form hypothesis; test one change at a time
|
|
15
|
+
4. Verify fix with targeted test
|
|
16
|
+
|
|
17
|
+
## CapInt
|
|
18
|
+
|
|
19
|
+
Matrix capability: `systematic-debugging` · Workflow: `/forge`
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# /forge
|
|
2
|
+
|
|
3
|
+
Default implementation workflow for medium/heavy tasks after confirm.
|
|
4
|
+
|
|
5
|
+
Inspired by harness-style phased orchestration; CapInt router selects this workflow for debug/refactor capabilities.
|
|
6
|
+
|
|
7
|
+
## Execution modes (from confirm option)
|
|
8
|
+
|
|
9
|
+
| Confirm option | Mode | Behavior |
|
|
10
|
+
|----------------|------|----------|
|
|
11
|
+
| `apply_now` | subagent | Single focused pass, minimal ceremony |
|
|
12
|
+
| `plan_first` | pipeline | Phased plan before edits |
|
|
13
|
+
| `analyze_only` | explore | Read-only analysis, no file writes |
|
|
14
|
+
|
|
15
|
+
## Phase 0 — Context
|
|
16
|
+
|
|
17
|
+
1. Read `AGENT.md`, `design.md`, optional `HANDOFF.md`
|
|
18
|
+
2. Use `context_pack` from Execution Intent when memory is optional/required
|
|
19
|
+
3. If ambiguous, stop and ask one clarifying question
|
|
20
|
+
|
|
21
|
+
## Phase 1 — Intent lock
|
|
22
|
+
|
|
23
|
+
1. Restate capability + resolution from Execution Intent
|
|
24
|
+
2. List assumptions and success criteria
|
|
25
|
+
3. Wait for confirm unless `apply_now` on light tasks
|
|
26
|
+
|
|
27
|
+
## Phase 2 — Execute
|
|
28
|
+
|
|
29
|
+
1. Apply smallest correct change set
|
|
30
|
+
2. Match project conventions; no drive-by refactors
|
|
31
|
+
3. For `producer_reviewer` pattern: implement then self-review before claiming done
|
|
32
|
+
|
|
33
|
+
## Phase 3 — Verify
|
|
34
|
+
|
|
35
|
+
1. Run verification hints from Execution Intent
|
|
36
|
+
2. Evidence before claims — no "done" without command output
|
|
37
|
+
3. If verification fails, return to Phase 2 (max 2 retries)
|
|
38
|
+
|
|
39
|
+
## Phase 4 — Handoff
|
|
40
|
+
|
|
41
|
+
1. Summarize what changed and why
|
|
42
|
+
2. Note follow-on capabilities from `orchestration.chains` if any
|
|
43
|
+
3. Update `HANDOFF.md` when session material decisions were made
|
|
44
|
+
|
|
45
|
+
## Error handling
|
|
46
|
+
|
|
47
|
+
| Situation | Action |
|
|
48
|
+
|-----------|--------|
|
|
49
|
+
| Blocked on missing info | `NEEDS_CONTEXT` — ask user, do not guess |
|
|
50
|
+
| Verification failed | Fix and re-verify; do not skip |
|
|
51
|
+
| Scope creep detected | Split task; route again via `capint consult` |
|