@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,115 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { loadMatrix } = require("./route-engine");
|
|
4
|
+
const { validateExecutionIntent } = require("./contract");
|
|
5
|
+
|
|
6
|
+
function loadRegistry(rootDir) {
|
|
7
|
+
const p = path.join(rootDir, "registry.json");
|
|
8
|
+
if (!fs.existsSync(p)) return null;
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
11
|
+
} catch (e) {
|
|
12
|
+
return { parseError: e.message };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function skillsOnDisk(rootDir) {
|
|
17
|
+
const skillsDir = path.join(rootDir, "skills");
|
|
18
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
19
|
+
return fs
|
|
20
|
+
.readdirSync(skillsDir)
|
|
21
|
+
.filter((n) => fs.existsSync(path.join(skillsDir, n, "SKILL.md")));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runDoctor(rootDir) {
|
|
25
|
+
const issues = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const ok = [];
|
|
28
|
+
|
|
29
|
+
const matrix = loadMatrix(rootDir);
|
|
30
|
+
if (!matrix) issues.push("skill-routing-matrix.json missing or invalid");
|
|
31
|
+
else ok.push(`matrix schema_version ${matrix.schema_version}`);
|
|
32
|
+
|
|
33
|
+
const registry = loadRegistry(rootDir);
|
|
34
|
+
if (!registry) issues.push("registry.json missing or invalid");
|
|
35
|
+
else if (registry.parseError) issues.push(`registry.json parse error: ${registry.parseError}`);
|
|
36
|
+
else ok.push("registry.json valid");
|
|
37
|
+
|
|
38
|
+
if (matrix && registry && !registry.parseError) {
|
|
39
|
+
const disk = new Set(skillsOnDisk(rootDir));
|
|
40
|
+
const allReg = new Set([
|
|
41
|
+
...(registry.core || []),
|
|
42
|
+
...(registry.recommended || []),
|
|
43
|
+
...(registry.optional || []),
|
|
44
|
+
...(registry.project_added || [])
|
|
45
|
+
]);
|
|
46
|
+
for (const name of registry.core || []) {
|
|
47
|
+
if (!disk.has(name)) warnings.push(`core skill missing on disk: ${name}`);
|
|
48
|
+
}
|
|
49
|
+
for (const tt of matrix.task_types || []) {
|
|
50
|
+
for (const s of tt.skills || []) {
|
|
51
|
+
if (!allReg.has(s) && !disk.has(s)) {
|
|
52
|
+
warnings.push(`task_type ${tt.id} references skill not in registry: ${s}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const ids = matrix.task_types.map((t) => t.id);
|
|
57
|
+
if (new Set(ids).size !== ids.length) issues.push("duplicate task_type ids in matrix");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!fs.existsSync(path.join(rootDir, "AGENT.md"))) {
|
|
61
|
+
warnings.push("AGENT.md missing — run capint init");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const policy = matrix?.provider_activation_policy;
|
|
65
|
+
if (policy) {
|
|
66
|
+
ok.push("provider_activation_policy present (default off)");
|
|
67
|
+
} else {
|
|
68
|
+
warnings.push("provider_activation_policy missing — memory/graph always local fallback");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const pass = issues.length === 0;
|
|
72
|
+
return {
|
|
73
|
+
pass,
|
|
74
|
+
exit_code: pass ? 0 : 1,
|
|
75
|
+
issues,
|
|
76
|
+
warnings,
|
|
77
|
+
ok,
|
|
78
|
+
auto_fix: false,
|
|
79
|
+
hint: pass ? "No auto-fix performed (by design)." : "Resolve issues manually; doctor does not mutate files."
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function runStatus(rootDir) {
|
|
84
|
+
const matrix = loadMatrix(rootDir);
|
|
85
|
+
const registry = loadRegistry(rootDir);
|
|
86
|
+
const doctor = runDoctor(rootDir);
|
|
87
|
+
const contextPath = path.join(rootDir, ".capint", "context.json");
|
|
88
|
+
let context = null;
|
|
89
|
+
if (fs.existsSync(contextPath)) {
|
|
90
|
+
try {
|
|
91
|
+
context = JSON.parse(fs.readFileSync(contextPath, "utf-8"));
|
|
92
|
+
} catch {
|
|
93
|
+
context = { error: "invalid json" };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
capint_package: require(path.join(__dirname, "..", "..", "package.json")).version,
|
|
99
|
+
matrix_version: matrix?.schema_version || null,
|
|
100
|
+
matrix_updated: matrix?.updated_at || null,
|
|
101
|
+
registry_schema: registry?.schema_version || null,
|
|
102
|
+
skills_on_disk: skillsOnDisk(rootDir).length,
|
|
103
|
+
core_skills: (registry?.core || []).length,
|
|
104
|
+
doctor_pass: doctor.pass,
|
|
105
|
+
provider_defaults: {
|
|
106
|
+
memory: matrix?.provider_activation_policy?.memory_providers?.default || "off",
|
|
107
|
+
graph: matrix?.provider_activation_policy?.graph_providers?.default || "off"
|
|
108
|
+
},
|
|
109
|
+
context,
|
|
110
|
+
warnings_count: doctor.warnings.length,
|
|
111
|
+
issues_count: doctor.issues.length
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { runDoctor, runStatus, skillsOnDisk, loadRegistry };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
|
|
5
|
+
const LOG_DIR = ".capint/logs";
|
|
6
|
+
const LOG_FILE = "route-events.ndjson";
|
|
7
|
+
|
|
8
|
+
function eventLogEnabled(rootDir) {
|
|
9
|
+
if (process.env.CAPINT_EVENT_LOG === "1") return true;
|
|
10
|
+
const ctxPath = path.join(rootDir, ".capint", "context.json");
|
|
11
|
+
if (!fs.existsSync(ctxPath)) return false;
|
|
12
|
+
try {
|
|
13
|
+
const ctx = JSON.parse(fs.readFileSync(ctxPath, "utf-8"));
|
|
14
|
+
return Boolean(ctx.event_log);
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function appendRouteEvents(rootDir, events) {
|
|
21
|
+
if (!rootDir || !Array.isArray(events) || events.length === 0) return { written: false };
|
|
22
|
+
if (!eventLogEnabled(rootDir)) return { written: false, reason: "disabled" };
|
|
23
|
+
|
|
24
|
+
const dir = path.join(rootDir, LOG_DIR);
|
|
25
|
+
const file = path.join(dir, LOG_FILE);
|
|
26
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
|
|
28
|
+
const ts = new Date().toISOString();
|
|
29
|
+
const lines = events.map((e) =>
|
|
30
|
+
JSON.stringify({
|
|
31
|
+
event_id: crypto.randomUUID(),
|
|
32
|
+
ts,
|
|
33
|
+
...e
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
fs.appendFileSync(file, `${lines.join("\n")}\n`, "utf-8");
|
|
37
|
+
return { written: true, path: path.join(LOG_DIR, LOG_FILE), count: events.length };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { appendRouteEvents, eventLogEnabled, LOG_DIR, LOG_FILE };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { resolveOrchestration } = require("./orchestration");
|
|
4
|
+
|
|
5
|
+
function resolveInstallationStatus(routeResult, rootDir) {
|
|
6
|
+
const winner = routeResult.winner;
|
|
7
|
+
if (!winner) {
|
|
8
|
+
return { resolution_status: "installed", resolution_hint: null };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (winner.type === "skill") {
|
|
12
|
+
const allSkills = routeResult.rows.flatMap((r) => r.skills);
|
|
13
|
+
const match = allSkills.find((s) => s.name === winner.resource);
|
|
14
|
+
if (match?.status === "not_installed") {
|
|
15
|
+
return {
|
|
16
|
+
resolution_status: "not_installed",
|
|
17
|
+
resolution_hint: `Skill "${winner.resource}" not installed — run capint init or add skills/${winner.resource}/SKILL.md`
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (winner.type === "workflow" && rootDir) {
|
|
23
|
+
const wfPath = path.join(rootDir, "workflows", `${winner.resource}.md`);
|
|
24
|
+
if (!fs.existsSync(wfPath)) {
|
|
25
|
+
return {
|
|
26
|
+
resolution_status: "not_installed",
|
|
27
|
+
resolution_hint: `Workflow "${winner.resource}" missing — run capint init to copy skill bundle`
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { resolution_status: "installed", resolution_hint: null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildConfirmBlock(text, overallWeight, matrix) {
|
|
36
|
+
const policy = matrix?.clarification_policy || {};
|
|
37
|
+
const mode = policy.mode || "adaptive";
|
|
38
|
+
const shouldAsk = mode === "always_confirm" || overallWeight !== "light";
|
|
39
|
+
if (!shouldAsk) return { enabled: false, mode, prompt: null, options: [], default_option: null };
|
|
40
|
+
|
|
41
|
+
const options = [
|
|
42
|
+
{ id: "apply_now", label: "Apply now" },
|
|
43
|
+
{ id: "plan_first", label: "Show short plan first" },
|
|
44
|
+
{ id: "analyze_only", label: "Analyze only, no code" }
|
|
45
|
+
];
|
|
46
|
+
const maxOptions = Math.max(1, Math.min(policy.max_options || 3, options.length));
|
|
47
|
+
const shortened = options.slice(0, maxOptions);
|
|
48
|
+
const defaultOption = policy.default_option || (overallWeight === "light" ? "apply_now" : "plan_first");
|
|
49
|
+
return {
|
|
50
|
+
enabled: true,
|
|
51
|
+
mode,
|
|
52
|
+
prompt: `How should I proceed? (${text.slice(0, 80)}${text.length > 80 ? "..." : ""})`,
|
|
53
|
+
options: shortened,
|
|
54
|
+
default_option: shortened.some((o) => o.id === defaultOption) ? defaultOption : shortened[0].id
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildVerificationHints(overallWeight, primaryTaskType, matrix) {
|
|
59
|
+
const defaults = matrix?.verification_defaults || {};
|
|
60
|
+
const profile = primaryTaskType?.verification_profile || defaults.profile || "standard";
|
|
61
|
+
const taskRequires = Boolean(primaryTaskType?.verification_required);
|
|
62
|
+
|
|
63
|
+
if (overallWeight === "light" && !defaults.require_on_light && !taskRequires) {
|
|
64
|
+
return { required: false, hints: [], profile };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const hints = [];
|
|
68
|
+
hints.push("Run a targeted test or lint command before claiming done");
|
|
69
|
+
if (profile === "security") {
|
|
70
|
+
hints.push("Verify auth/session paths and no secret leakage");
|
|
71
|
+
} else if (profile === "debug") {
|
|
72
|
+
hints.push("Reproduce bug, apply fix, re-run failing case");
|
|
73
|
+
} else if (profile === "refactor") {
|
|
74
|
+
hints.push("Existing tests pass; no behavior change unless intended");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const required =
|
|
78
|
+
overallWeight === "heavy" ||
|
|
79
|
+
overallWeight === "medium" ||
|
|
80
|
+
Boolean(primaryTaskType?.verification_required);
|
|
81
|
+
|
|
82
|
+
return { required, hints, profile };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildRouteExplain(routeResult, parsedIntent, matrix) {
|
|
86
|
+
const primary = routeResult.taskTypeResolved;
|
|
87
|
+
return {
|
|
88
|
+
matched_task_type: primary?.id || null,
|
|
89
|
+
resolver_order: matrix?.resolver_order || [],
|
|
90
|
+
winner: routeResult.winner
|
|
91
|
+
? { name: routeResult.winner.name, resource: routeResult.winner.resource, confidence: routeResult.confidence }
|
|
92
|
+
: null,
|
|
93
|
+
overrides: parsedIntent?.overrides || {},
|
|
94
|
+
overall_weight: routeResult.overall
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function buildExecutionPolicy({ rawText, routeResult, memoryStrategy, matrix, parsedIntent, rootDir }) {
|
|
99
|
+
const primaryTaskType = routeResult.taskTypeResolved;
|
|
100
|
+
const rows = routeResult.rows;
|
|
101
|
+
const overallWeight = routeResult.overall;
|
|
102
|
+
const fallbackCapability =
|
|
103
|
+
matrix?.fallback_policy?.ambiguous_default_capability || primaryTaskType?.id || "vague_request";
|
|
104
|
+
const capability = primaryTaskType?.capability || fallbackCapability;
|
|
105
|
+
const installStatus = resolveInstallationStatus(routeResult, rootDir);
|
|
106
|
+
let plan =
|
|
107
|
+
primaryTaskType?.default_execution_policy ||
|
|
108
|
+
matrix?.execution_policy_defaults?.[overallWeight] ||
|
|
109
|
+
(overallWeight === "light" ? "auto" : "confirm");
|
|
110
|
+
if (installStatus.resolution_status === "not_installed") {
|
|
111
|
+
plan = "confirm";
|
|
112
|
+
}
|
|
113
|
+
const confirmBlock = buildConfirmBlock(rawText, overallWeight, matrix);
|
|
114
|
+
const ambiguous = rows.some((r) => r.reason.includes("ambiguous default"));
|
|
115
|
+
const verification = buildVerificationHints(overallWeight, primaryTaskType, matrix);
|
|
116
|
+
const orchestration = resolveOrchestration(primaryTaskType, confirmBlock.default_option, matrix);
|
|
117
|
+
|
|
118
|
+
const out = {
|
|
119
|
+
intent: rawText,
|
|
120
|
+
capability,
|
|
121
|
+
resolution: `${routeResult.winner.name}/${routeResult.winner.resource}`,
|
|
122
|
+
memory: memoryStrategy?.memory || primaryTaskType?.requires_memory || "optional",
|
|
123
|
+
plan,
|
|
124
|
+
confirm: confirmBlock,
|
|
125
|
+
confirm_question: confirmBlock.prompt,
|
|
126
|
+
confirm_options: confirmBlock.options,
|
|
127
|
+
confirm_default_option: confirmBlock.default_option,
|
|
128
|
+
fallback: ambiguous ? { capability, override: matrix?.fallback_policy?.override_hint || "/workflow" } : null,
|
|
129
|
+
verification_required: verification.required,
|
|
130
|
+
verification_hints: verification.hints,
|
|
131
|
+
verification_profile: verification.profile,
|
|
132
|
+
provider_activation: memoryStrategy?.provider_activation || null,
|
|
133
|
+
context_pack: memoryStrategy?.context_pack || null,
|
|
134
|
+
orchestration,
|
|
135
|
+
route_explain: buildRouteExplain(routeResult, parsedIntent, matrix),
|
|
136
|
+
resolution_status: installStatus.resolution_status,
|
|
137
|
+
resolution_hint: installStatus.resolution_hint
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (memoryStrategy?.local_adapters) {
|
|
141
|
+
out.local_adapters = memoryStrategy.local_adapters;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (confirmBlock.default_option === "analyze_only" && verification.required) {
|
|
145
|
+
out.verification_hints = [
|
|
146
|
+
...verification.hints,
|
|
147
|
+
"analyze_only: produce evidence-backed findings without code edits"
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (installStatus.resolution_status === "not_installed") {
|
|
152
|
+
out.verification_required = true;
|
|
153
|
+
out.verification_hints = [
|
|
154
|
+
...(out.verification_hints || []),
|
|
155
|
+
"install skill bundle or add SKILL.md before apply"
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
buildExecutionPolicy,
|
|
164
|
+
buildConfirmBlock,
|
|
165
|
+
buildVerificationHints,
|
|
166
|
+
buildRouteExplain,
|
|
167
|
+
resolveInstallationStatus
|
|
168
|
+
};
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE projection sync — capint/projections/session-start.md → native IDE entry files.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
const DEFAULT_TARGETS = ["cursor", "claude", "gemini", "antigravity"];
|
|
9
|
+
|
|
10
|
+
const CAPINT_SYNC_MARKER =
|
|
11
|
+
"<!-- source: capint/projections/session-start.md | capint ide sync -->";
|
|
12
|
+
|
|
13
|
+
function pkgRoot() {
|
|
14
|
+
return path.join(__dirname, "..", "..");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readSessionStart() {
|
|
18
|
+
const p = path.join(pkgRoot(), "projections", "session-start.md");
|
|
19
|
+
if (!fs.existsSync(p)) return null;
|
|
20
|
+
return fs.readFileSync(p, "utf-8");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function stripFrontmatter(text) {
|
|
24
|
+
if (!text.startsWith("---")) return text;
|
|
25
|
+
const end = text.indexOf("---", 3);
|
|
26
|
+
if (end === -1) return text;
|
|
27
|
+
return text.slice(end + 3).trimStart();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function syncCursorRule(cwd, body, dryRun) {
|
|
31
|
+
const dir = path.join(cwd, ".cursor", "rules");
|
|
32
|
+
const file = path.join(dir, "00-capint-session.mdc");
|
|
33
|
+
const content = `---
|
|
34
|
+
description: CapInt session start + Execution Intent (always apply)
|
|
35
|
+
alwaysApply: true
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
${CAPINT_SYNC_MARKER}
|
|
39
|
+
|
|
40
|
+
${body}
|
|
41
|
+
`;
|
|
42
|
+
if (!dryRun) {
|
|
43
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
+
fs.writeFileSync(file, content);
|
|
45
|
+
}
|
|
46
|
+
return file;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function syncClaudeMd(cwd, body, dryRun) {
|
|
50
|
+
const file = path.join(cwd, "CLAUDE.md");
|
|
51
|
+
const content = `${CAPINT_SYNC_MARKER}
|
|
52
|
+
|
|
53
|
+
# Claude Code — CapInt
|
|
54
|
+
|
|
55
|
+
Read root **AGENT.md** for project rules.
|
|
56
|
+
|
|
57
|
+
${body}
|
|
58
|
+
`;
|
|
59
|
+
if (!dryRun) fs.writeFileSync(file, content);
|
|
60
|
+
return file;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function syncGeminiMd(cwd, body, dryRun) {
|
|
64
|
+
const file = path.join(cwd, "GEMINI.md");
|
|
65
|
+
const content = `${CAPINT_SYNC_MARKER}
|
|
66
|
+
|
|
67
|
+
# Gemini — CapInt
|
|
68
|
+
|
|
69
|
+
Read root **AGENT.md** for project rules.
|
|
70
|
+
|
|
71
|
+
${body}
|
|
72
|
+
`;
|
|
73
|
+
if (!dryRun) fs.writeFileSync(file, content);
|
|
74
|
+
return file;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function syncAntigravity(cwd, body, dryRun) {
|
|
78
|
+
const written = [];
|
|
79
|
+
const agentsDir = path.join(cwd, ".agents");
|
|
80
|
+
const rulesDir = path.join(agentsDir, "rules");
|
|
81
|
+
const skillsDir = path.join(agentsDir, "skills", "capint-router");
|
|
82
|
+
|
|
83
|
+
const sessionBody = stripFrontmatter(body);
|
|
84
|
+
const sessionFile = path.join(rulesDir, "00-capint-session.md");
|
|
85
|
+
const indexFile = path.join(agentsDir, "CAPINT.md");
|
|
86
|
+
const routerSkill = path.join(skillsDir, "SKILL.md");
|
|
87
|
+
|
|
88
|
+
const sessionContent = `${CAPINT_SYNC_MARKER}
|
|
89
|
+
|
|
90
|
+
# CapInt session + task routing
|
|
91
|
+
|
|
92
|
+
${sessionBody}
|
|
93
|
+
`;
|
|
94
|
+
const indexContent = `${CAPINT_SYNC_MARKER}
|
|
95
|
+
|
|
96
|
+
# CapInt
|
|
97
|
+
|
|
98
|
+
Canonical project files: \`AGENT.md\`, \`.capint/\` — run \`capint ide sync\` after init.
|
|
99
|
+
`;
|
|
100
|
+
const routerContent = `---
|
|
101
|
+
name: capint-router
|
|
102
|
+
description: Routes agent to CapInt session protocol and skill-routing-matrix.json.
|
|
103
|
+
origin: capint
|
|
104
|
+
capint_bundle: antigravity-projection
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
# CapInt Router (Antigravity)
|
|
108
|
+
|
|
109
|
+
1. Session: \`.agents/rules/00-capint-session.md\` + \`AGENT.md\`
|
|
110
|
+
2. Task routing: \`capint route "your task"\`
|
|
111
|
+
3. Skills: \`skills/<name>/SKILL.md\`
|
|
112
|
+
|
|
113
|
+
Index: \`.agents/CAPINT.md\`
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
if (!dryRun) {
|
|
117
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
118
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
119
|
+
fs.writeFileSync(sessionFile, sessionContent);
|
|
120
|
+
fs.writeFileSync(indexFile, indexContent);
|
|
121
|
+
fs.writeFileSync(routerSkill, routerContent);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
written.push(sessionFile, indexFile, routerSkill);
|
|
125
|
+
return written;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function updateContextJson(cwd, targets, dryRun) {
|
|
129
|
+
const file = path.join(cwd, ".capint", "context.json");
|
|
130
|
+
if (!fs.existsSync(file)) return null;
|
|
131
|
+
let ctx;
|
|
132
|
+
try {
|
|
133
|
+
ctx = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
ctx.ide_projection = {
|
|
138
|
+
targets: [...targets],
|
|
139
|
+
last_synced: new Date().toISOString().slice(0, 10),
|
|
140
|
+
canonical: "capint/projections/session-start.md",
|
|
141
|
+
marker: CAPINT_SYNC_MARKER
|
|
142
|
+
};
|
|
143
|
+
if (!dryRun) fs.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}\n`);
|
|
144
|
+
return file;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function syncIdeProjections(cwd, options = {}) {
|
|
148
|
+
const { targets = DEFAULT_TARGETS, dryRun = false } = options;
|
|
149
|
+
const result = { written: [], skipped: [], errors: [] };
|
|
150
|
+
|
|
151
|
+
const capintDir = path.join(cwd, ".capint");
|
|
152
|
+
if (!fs.existsSync(capintDir) && !fs.existsSync(path.join(cwd, "AGENT.md"))) {
|
|
153
|
+
result.errors.push("No CapInt project — run capint init first");
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const raw = readSessionStart();
|
|
158
|
+
if (!raw) {
|
|
159
|
+
result.errors.push("session-start.md not found in package projections/");
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
const body = stripFrontmatter(raw);
|
|
163
|
+
const set = new Set(targets.map((t) => t.toLowerCase()));
|
|
164
|
+
|
|
165
|
+
if (set.has("cursor")) result.written.push(syncCursorRule(cwd, body, dryRun));
|
|
166
|
+
if (set.has("claude") || set.has("claude-code")) {
|
|
167
|
+
result.written.push(syncClaudeMd(cwd, body, dryRun));
|
|
168
|
+
}
|
|
169
|
+
if (set.has("gemini") || set.has("gemini-cli")) {
|
|
170
|
+
result.written.push(syncGeminiMd(cwd, body, dryRun));
|
|
171
|
+
}
|
|
172
|
+
if (set.has("codex")) {
|
|
173
|
+
result.skipped.push("codex uses root AGENTS.md — ensure AGENTS.md exists");
|
|
174
|
+
}
|
|
175
|
+
if (set.has("antigravity")) {
|
|
176
|
+
result.written.push(...syncAntigravity(cwd, body, dryRun));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const ctxPath = updateContextJson(cwd, [...set], dryRun);
|
|
180
|
+
if (ctxPath) result.written.push(ctxPath);
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function checkIdeProjection(cwd) {
|
|
186
|
+
const marker = CAPINT_SYNC_MARKER;
|
|
187
|
+
function hasMarker(filePath) {
|
|
188
|
+
if (!fs.existsSync(filePath)) return false;
|
|
189
|
+
return fs.readFileSync(filePath, "utf-8").includes(marker);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return [
|
|
193
|
+
{
|
|
194
|
+
name: "Cursor .mdc",
|
|
195
|
+
ok: hasMarker(path.join(cwd, ".cursor", "rules", "00-capint-session.mdc")),
|
|
196
|
+
path: ".cursor/rules/00-capint-session.mdc"
|
|
197
|
+
},
|
|
198
|
+
{ name: "CLAUDE.md", ok: hasMarker(path.join(cwd, "CLAUDE.md")), path: "CLAUDE.md" },
|
|
199
|
+
{ name: "GEMINI.md", ok: hasMarker(path.join(cwd, "GEMINI.md")), path: "GEMINI.md" },
|
|
200
|
+
{
|
|
201
|
+
name: "Antigravity session",
|
|
202
|
+
ok: hasMarker(path.join(cwd, ".agents", "rules", "00-capint-session.md")),
|
|
203
|
+
path: ".agents/rules/00-capint-session.md"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "AGENTS.md",
|
|
207
|
+
ok: fs.existsSync(path.join(cwd, "AGENTS.md")),
|
|
208
|
+
path: "AGENTS.md"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "skill-routing-matrix.json",
|
|
212
|
+
ok: fs.existsSync(path.join(cwd, "skill-routing-matrix.json")),
|
|
213
|
+
path: "skill-routing-matrix.json"
|
|
214
|
+
}
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function appendIdeManifestEntries(rootDir, syncResult, dryRun) {
|
|
219
|
+
if (dryRun) return null;
|
|
220
|
+
const { readScaffoldManifest, writeScaffoldManifest } = require("./scaffold/manifest");
|
|
221
|
+
const { enrichFileEntry } = require("./scaffold/manifest-schema");
|
|
222
|
+
|
|
223
|
+
const manifestPath = path.join(rootDir, ".capint", "scaffold-manifest.json");
|
|
224
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
225
|
+
|
|
226
|
+
let manifest;
|
|
227
|
+
try {
|
|
228
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
229
|
+
} catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const existing = new Set((manifest.files || []).map((f) => f.path));
|
|
234
|
+
const relPaths = syncResult.written.map((abs) =>
|
|
235
|
+
path.relative(rootDir, abs).replace(/\\/g, "/")
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
for (const rel of relPaths) {
|
|
239
|
+
if (existing.has(rel)) continue;
|
|
240
|
+
manifest.files.push(
|
|
241
|
+
enrichFileEntry({
|
|
242
|
+
path: rel,
|
|
243
|
+
action: "created",
|
|
244
|
+
source: "capint",
|
|
245
|
+
removable: true,
|
|
246
|
+
kind: "ide_projection",
|
|
247
|
+
marker: CAPINT_SYNC_MARKER
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
manifest.last_run = new Date().toISOString();
|
|
253
|
+
const report = {
|
|
254
|
+
generated_at: manifest.last_run,
|
|
255
|
+
preset: manifest.preset,
|
|
256
|
+
summary: manifest.summary,
|
|
257
|
+
results: manifest.files.map((f) => ({
|
|
258
|
+
path: f.path,
|
|
259
|
+
action: f.action,
|
|
260
|
+
source: f.source,
|
|
261
|
+
removable: f.removable,
|
|
262
|
+
kind: f.kind,
|
|
263
|
+
marker: f.marker,
|
|
264
|
+
sidecar: f.sidecar
|
|
265
|
+
}))
|
|
266
|
+
};
|
|
267
|
+
writeScaffoldManifest(rootDir, report);
|
|
268
|
+
return manifestPath;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
module.exports = {
|
|
272
|
+
DEFAULT_TARGETS,
|
|
273
|
+
CAPINT_SYNC_MARKER,
|
|
274
|
+
syncIdeProjections,
|
|
275
|
+
checkIdeProjection,
|
|
276
|
+
appendIdeManifestEntries
|
|
277
|
+
};
|