@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.
Files changed (69) hide show
  1. package/AGENT.md +28 -0
  2. package/CHANGELOG.md +58 -0
  3. package/README.md +94 -0
  4. package/bin/capint.js +90 -0
  5. package/design.md +23 -0
  6. package/docs/architecture-decisions.md +95 -0
  7. package/docs/execution-intent-contract.md +81 -0
  8. package/docs/manifest-schema.md +36 -0
  9. package/docs/release-checklist.md +31 -0
  10. package/package.json +33 -0
  11. package/projections/session-start.md +32 -0
  12. package/registry.json +12 -0
  13. package/scripts/release-check.mjs +40 -0
  14. package/scripts/validate-matrix.mjs +83 -0
  15. package/skill-routing-matrix.json +150 -0
  16. package/skills/capability-router/SKILL.md +3 -0
  17. package/skills/context-memory-bridge/SKILL.md +17 -0
  18. package/skills/localization-hub/SKILL.md +17 -0
  19. package/skills/refactor/SKILL.md +17 -0
  20. package/skills/systematic-debugging/SKILL.md +19 -0
  21. package/src/commands/audit.js +32 -0
  22. package/src/commands/consult.js +64 -0
  23. package/src/commands/doctor.js +36 -0
  24. package/src/commands/ide.js +62 -0
  25. package/src/commands/init.js +50 -0
  26. package/src/commands/memory.js +60 -0
  27. package/src/commands/route.js +30 -0
  28. package/src/commands/scaffold.js +37 -0
  29. package/src/commands/status.js +22 -0
  30. package/src/commands/uninstall.js +46 -0
  31. package/src/commands/upgrade.js +69 -0
  32. package/src/lib/audit.js +107 -0
  33. package/src/lib/capability-router.js +118 -0
  34. package/src/lib/context-memory-bridge.js +87 -0
  35. package/src/lib/context-pack.js +39 -0
  36. package/src/lib/contract.js +71 -0
  37. package/src/lib/doctor.js +115 -0
  38. package/src/lib/event-log.js +40 -0
  39. package/src/lib/execution-policy.js +168 -0
  40. package/src/lib/ide-sync.js +277 -0
  41. package/src/lib/intent-parser.js +116 -0
  42. package/src/lib/orchestration.js +25 -0
  43. package/src/lib/providers/activation-policy.js +93 -0
  44. package/src/lib/providers/graph-provider.js +35 -0
  45. package/src/lib/providers/local-graph-adapter.js +40 -0
  46. package/src/lib/providers/local-memory-adapter.js +41 -0
  47. package/src/lib/providers/memory-provider.js +35 -0
  48. package/src/lib/route-engine.js +191 -0
  49. package/src/lib/scaffold/file-policy.js +92 -0
  50. package/src/lib/scaffold/index.js +29 -0
  51. package/src/lib/scaffold/manifest-schema.js +116 -0
  52. package/src/lib/scaffold/manifest.js +34 -0
  53. package/src/lib/scaffold/presets.js +132 -0
  54. package/src/lib/uninstall.js +126 -0
  55. package/src/lib/upgrade-matrix.js +120 -0
  56. package/templates/bundle/skills/capability-router/SKILL.md +12 -0
  57. package/templates/bundle/skills/context-memory-bridge/SKILL.md +17 -0
  58. package/templates/bundle/skills/localization-hub/SKILL.md +17 -0
  59. package/templates/bundle/skills/refactor/SKILL.md +17 -0
  60. package/templates/bundle/skills/systematic-debugging/SKILL.md +19 -0
  61. package/templates/bundle/workflows/forge.md +51 -0
  62. package/templates/minimal/.capint/rules/core.md +18 -0
  63. package/templates/minimal/AGENT.md +26 -0
  64. package/templates/minimal/AGENTS.md +9 -0
  65. package/templates/minimal/design.md +25 -0
  66. package/templates/minimal/registry.json +7 -0
  67. package/templates/prismx-compatible/.capint/context.json +8 -0
  68. package/templates/prismx-compatible/HANDOFF.md +19 -0
  69. package/workflows/forge.md +51 -0
@@ -0,0 +1,37 @@
1
+ const path = require("path");
2
+ const { runScaffold, PRESET_MANIFEST } = require("../lib/scaffold");
3
+
4
+ module.exports = async function scaffold(args, flags) {
5
+ const rootDir = process.cwd();
6
+ const preset = flags.preset || args.find((a) => !a.startsWith("--")) || "minimal";
7
+ const projectName = flags.name || path.basename(rootDir);
8
+ const force = Boolean(flags.force);
9
+
10
+ if (flags.list) {
11
+ console.log("\nAvailable presets:\n");
12
+ for (const [id, meta] of Object.entries(PRESET_MANIFEST)) {
13
+ console.log(` ${id.padEnd(20)} ${meta.description}`);
14
+ }
15
+ console.log("");
16
+ return;
17
+ }
18
+
19
+ const report = runScaffold({ rootDir, preset, projectName, force });
20
+ if (report.error) {
21
+ console.error(report.error);
22
+ process.exit(1);
23
+ }
24
+
25
+ if (flags.json) {
26
+ console.log(JSON.stringify(report, null, 2));
27
+ return;
28
+ }
29
+
30
+ console.log("\n## CapInt Scaffold\n");
31
+ console.log(`Preset: ${preset}`);
32
+ console.log(`Summary: created=${report.summary.created} updated=${report.summary.updated} skipped=${report.summary.skipped} conflict=${report.summary.conflict}\n`);
33
+ for (const r of report.results) {
34
+ console.log(` [${r.action}] ${r.path}${r.sidecar ? ` (sidecar: ${r.sidecar})` : ""}`);
35
+ }
36
+ console.log("");
37
+ };
@@ -0,0 +1,22 @@
1
+ const { runStatus } = require("../lib/doctor");
2
+
3
+ module.exports = async function status(_args, flags) {
4
+ const rootDir = process.cwd();
5
+ const data = runStatus(rootDir);
6
+
7
+ if (flags.json) {
8
+ console.log(JSON.stringify(data, null, 2));
9
+ return;
10
+ }
11
+
12
+ console.log("\n## CapInt Status (read-only)\n");
13
+ console.log(`Package: @bcelep/capint v${data.capint_package}`);
14
+ console.log(`Matrix: ${data.matrix_version || "—"} (${data.matrix_updated || "—"})`);
15
+ console.log(`Skills on disk: ${data.skills_on_disk} (core registry: ${data.core_skills})`);
16
+ console.log(`Doctor: ${data.doctor_pass ? "pass" : "fail"} (issues: ${data.issues_count}, warnings: ${data.warnings_count})`);
17
+ console.log(`Providers default: memory=${data.provider_defaults.memory}, graph=${data.provider_defaults.graph}`);
18
+ if (data.context) {
19
+ console.log(`Context preset: ${data.context.preset || "—"}`);
20
+ }
21
+ console.log("");
22
+ };
@@ -0,0 +1,46 @@
1
+ const path = require("path");
2
+ const { buildUninstallPlan, executeUninstall } = require("../lib/uninstall");
3
+
4
+ module.exports = async function uninstall(flags = {}) {
5
+ const rootDir = process.cwd();
6
+ const dryRun = Boolean(flags["dry-run"]);
7
+ const keepAgent = Boolean(flags["keep-agent"]);
8
+ const keepIde = Boolean(flags["keep-ide"]);
9
+ const includeSidecars = Boolean(flags["include-sidecars"]);
10
+ const yes = Boolean(flags.yes || flags.y);
11
+
12
+ const plan = buildUninstallPlan(rootDir, { keepAgent, keepIde, includeSidecars });
13
+
14
+ if (flags.json) {
15
+ console.log(JSON.stringify({ dry_run: dryRun, plan: plan.map((p) => p.path) }, null, 2));
16
+ if (dryRun) return;
17
+ } else {
18
+ console.log("\n## CapInt Uninstall\n");
19
+ if (!plan.length) {
20
+ console.log("Nothing to remove.\n");
21
+ return;
22
+ }
23
+ console.log("Will remove:");
24
+ for (const p of plan) console.log(` - ${p.path}`);
25
+ if (keepAgent) console.log("\nKeeping: AGENT.md, design.md, AGENTS.md (--keep-agent)");
26
+ if (keepIde) console.log("Keeping: IDE projections (--keep-ide)");
27
+ console.log("");
28
+ }
29
+
30
+ if (dryRun) {
31
+ if (!flags.json) console.log("Dry-run — no files deleted.\n");
32
+ return;
33
+ }
34
+
35
+ if (!yes && !flags.json) {
36
+ console.error("Add --yes to confirm removal.");
37
+ process.exit(1);
38
+ }
39
+
40
+ const result = executeUninstall(rootDir, plan, false);
41
+ if (!flags.json) {
42
+ for (const p of result.removed) console.log(` removed ${p}`);
43
+ for (const p of result.skipped) console.log(` skipped ${p}`);
44
+ console.log("\nDone. Global CLI: npm uninstall -g @bcelep/capint\n");
45
+ }
46
+ };
@@ -0,0 +1,69 @@
1
+ const path = require("path");
2
+ const { planUpgrade, applyUpgrade } = require("../lib/upgrade-matrix");
3
+
4
+ function pkgRoot() {
5
+ return path.join(__dirname, "..", "..");
6
+ }
7
+
8
+ module.exports = async function upgrade(flags = {}) {
9
+ const rootDir = process.cwd();
10
+ const dryRun = !flags.apply;
11
+ const plan = planUpgrade(rootDir, pkgRoot());
12
+
13
+ if (plan.error) {
14
+ console.error(plan.error);
15
+ process.exit(1);
16
+ }
17
+
18
+ if (flags.json) {
19
+ console.log(
20
+ JSON.stringify(
21
+ {
22
+ dry_run: dryRun,
23
+ project_schema: plan.project_schema,
24
+ package_schema: plan.package_schema,
25
+ conflicts: plan.conflicts,
26
+ custom_preserved: plan.custom_preserved,
27
+ summary: plan.summary
28
+ },
29
+ null,
30
+ 2
31
+ )
32
+ );
33
+ if (dryRun) return;
34
+ } else {
35
+ console.log("\n## CapInt Upgrade (matrix)\n");
36
+ console.log(`Project schema: ${plan.project_schema}`);
37
+ console.log(`Package schema: ${plan.package_schema}`);
38
+ console.log(`\n${plan.summary}\n`);
39
+
40
+ if (plan.custom_preserved.length) {
41
+ console.log("Custom preserved task_types:");
42
+ for (const id of plan.custom_preserved) console.log(` + ${id}`);
43
+ console.log("");
44
+ }
45
+
46
+ if (plan.conflicts.length) {
47
+ console.log("Conflicts (package wins on --apply):");
48
+ for (const c of plan.conflicts) {
49
+ console.log(` ${c.id}.${c.field}`);
50
+ console.log(` project: ${JSON.stringify(c.project_value)}`);
51
+ console.log(` package: ${JSON.stringify(c.package_value)}`);
52
+ }
53
+ console.log("");
54
+ } else {
55
+ console.log("No field conflicts.\n");
56
+ }
57
+ }
58
+
59
+ if (dryRun) {
60
+ if (!flags.json) console.log("Dry-run — use --apply to write backup + update matrix.\n");
61
+ return;
62
+ }
63
+
64
+ const backup = applyUpgrade(rootDir, plan.merged);
65
+ if (!flags.json) {
66
+ console.log(`Backup: ${path.relative(rootDir, backup)}`);
67
+ console.log("Matrix updated.\n");
68
+ }
69
+ };
@@ -0,0 +1,107 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { runDoctor } = require("./doctor");
4
+ const { loadMatrix } = require("./route-engine");
5
+
6
+ const EXPECTED_AGENT_MARKERS = ["Execution Intent", "confirm", "parseIntent"];
7
+
8
+ function fileExists(rootDir, rel) {
9
+ return fs.existsSync(path.join(rootDir, rel));
10
+ }
11
+
12
+ function readText(rootDir, rel) {
13
+ const p = path.join(rootDir, rel);
14
+ if (!fs.existsSync(p)) return null;
15
+ return fs.readFileSync(p, "utf-8");
16
+ }
17
+
18
+ function runAudit(rootDir) {
19
+ const doctor = runDoctor(rootDir);
20
+ const matrix = loadMatrix(rootDir);
21
+ const findings = [];
22
+ const recommendations = [];
23
+
24
+ const agentMd = readText(rootDir, "AGENT.md");
25
+ const designMd = readText(rootDir, "design.md");
26
+ const handoffMd = readText(rootDir, "HANDOFF.md");
27
+ const manifestPath = path.join(rootDir, ".capint", "scaffold-manifest.json");
28
+
29
+ if (!agentMd) {
30
+ findings.push({ level: "warning", code: "missing_agent_md", message: "AGENT.md missing" });
31
+ recommendations.push("capint init --preset minimal");
32
+ } else {
33
+ for (const marker of EXPECTED_AGENT_MARKERS) {
34
+ if (!agentMd.includes(marker)) {
35
+ findings.push({
36
+ level: "info",
37
+ code: "agent_contract_drift",
38
+ message: `AGENT.md missing expected marker: ${marker}`
39
+ });
40
+ }
41
+ }
42
+ }
43
+
44
+ if (!designMd) {
45
+ findings.push({ level: "warning", code: "missing_design_md", message: "design.md missing" });
46
+ recommendations.push("capint init --preset minimal");
47
+ }
48
+
49
+ if (fileExists(rootDir, ".capint/context.json") && !handoffMd) {
50
+ findings.push({
51
+ level: "info",
52
+ code: "handoff_optional",
53
+ message: "prismx-compatible context present but HANDOFF.md missing"
54
+ });
55
+ recommendations.push("capint scaffold --preset prismx-compatible");
56
+ }
57
+
58
+ if (fs.existsSync(manifestPath)) {
59
+ try {
60
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
61
+ const conflicts = (manifest.files || []).filter((f) => f.action === "conflict").length;
62
+ if (conflicts > 0) {
63
+ findings.push({
64
+ level: "warning",
65
+ code: "scaffold_conflicts",
66
+ message: `${conflicts} scaffold conflict(s) unresolved — review *.capint.new.md files`
67
+ });
68
+ }
69
+ } catch {
70
+ findings.push({ level: "issue", code: "bad_scaffold_manifest", message: "scaffold-manifest.json invalid JSON" });
71
+ }
72
+ }
73
+
74
+ if (matrix) {
75
+ const withoutPattern = (matrix.task_types || []).filter((t) => !t.orchestration_pattern).map((t) => t.id);
76
+ if (withoutPattern.length) {
77
+ findings.push({
78
+ level: "info",
79
+ code: "matrix_patterns_incomplete",
80
+ message: `task_types without orchestration_pattern: ${withoutPattern.join(", ")}`
81
+ });
82
+ }
83
+ }
84
+
85
+ for (const issue of doctor.issues) {
86
+ findings.push({ level: "issue", code: "doctor", message: issue });
87
+ }
88
+ for (const w of doctor.warnings) {
89
+ findings.push({ level: "warning", code: "doctor", message: w });
90
+ }
91
+
92
+ const issueCount = findings.filter((f) => f.level === "issue").length;
93
+ const warningCount = findings.filter((f) => f.level === "warning").length;
94
+
95
+ return {
96
+ mode: "read_only",
97
+ side_effects: false,
98
+ pass: issueCount === 0,
99
+ exit_code: issueCount === 0 ? 0 : 1,
100
+ summary: { issues: issueCount, warnings: warningCount, info: findings.filter((f) => f.level === "info").length },
101
+ findings,
102
+ recommendations: [...new Set(recommendations)],
103
+ doctor_pass: doctor.pass
104
+ };
105
+ }
106
+
107
+ module.exports = { runAudit };
@@ -0,0 +1,118 @@
1
+ const WEIGHT_RANK = { light: 0, medium: 1, heavy: 2 };
2
+ const WEIGHT_EMOJI = { light: "🟢", medium: "🟡", heavy: "🔴" };
3
+
4
+ function applyFileThresholdSignals(classification, matrix) {
5
+ const estimated = classification?.signals?.estimated_files;
6
+ if (!estimated || !matrix?.classification?.file_thresholds) return classification;
7
+ const next = { ...classification, reasons: [...(classification.reasons || [])] };
8
+ const thresholds = matrix.classification.file_thresholds;
9
+ if (estimated >= (thresholds.heavy || Number.MAX_SAFE_INTEGER)) {
10
+ next.reasons.push(`file_threshold:heavy(${estimated})`);
11
+ } else if (estimated >= (thresholds.medium || Number.MAX_SAFE_INTEGER)) {
12
+ next.reasons.push(`file_threshold:medium(${estimated})`);
13
+ } else {
14
+ next.reasons.push(`file_threshold:light(${estimated})`);
15
+ }
16
+ return next;
17
+ }
18
+
19
+ function skillStatus(name, registry, installed) {
20
+ if (!installed.includes(name)) return "not_installed";
21
+ for (const cat of ["core", "recommended", "optional", "project_added"]) {
22
+ if ((registry[cat] || []).includes(name)) return cat;
23
+ }
24
+ return "on_disk";
25
+ }
26
+
27
+ function collectSkills(text, matrix, classification, matchKeywords) {
28
+ const skills = new Set();
29
+ let workflow = null;
30
+ let rulesHint = null;
31
+ const order = matrix?.resolver_order || ["exact_keyword", "domain_rules", "classification_default"];
32
+
33
+ if (order.includes("exact_keyword") && classification.taskType) {
34
+ for (const s of classification.taskType.skills || []) skills.add(s);
35
+ workflow = classification.taskType.workflow || null;
36
+ rulesHint = classification.taskType.rules_hint || null;
37
+ }
38
+
39
+ if (order.includes("domain_rules")) {
40
+ for (const tt of matrix.task_types || []) {
41
+ if (matchKeywords(text, tt.keywords || [])) {
42
+ for (const s of tt.skills || []) skills.add(s);
43
+ if (tt.workflow) workflow = tt.workflow;
44
+ if (tt.rules_hint) rulesHint = tt.rules_hint;
45
+ }
46
+ }
47
+ for (const [ruleFile, meta] of Object.entries(matrix.rules_domain_map || {})) {
48
+ const domain = ruleFile.replace("rules/", "").replace(".md", "");
49
+ if (matchKeywords(text, [domain, ...domain.split("-")])) {
50
+ const minWeight = meta.weight_min || "light";
51
+ if (WEIGHT_RANK[classification.weight] >= WEIGHT_RANK[minWeight]) {
52
+ for (const s of meta.skills || []) skills.add(s);
53
+ rulesHint = rulesHint || ruleFile;
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ if (order.includes("classification_default") && !skills.size) skills.add("capability-router");
60
+ return { skills: [...skills], workflow, rulesHint };
61
+ }
62
+
63
+ function deriveProviders(taskType, skills, workflow) {
64
+ if (taskType?.providers?.length) return taskType.providers;
65
+ const providers = [];
66
+ if (workflow) providers.push({ name: "workflow", type: "workflow", resource: workflow.replace(/^\//, ""), confidence_base: 0.8 });
67
+ if (skills?.length) providers.push({ name: "skill", type: "skill", resource: skills[0], confidence_base: 0.75 });
68
+ if (!providers.length) providers.push({ name: "fallback", type: "skill", resource: "capability-router", confidence_base: 0.5 });
69
+ return providers;
70
+ }
71
+
72
+ function pickProvider(providers, resolverOrder = []) {
73
+ const sorted = [...providers].sort((a, b) => (b.confidence_base || 0) - (a.confidence_base || 0));
74
+ if (!resolverOrder.length) return sorted[0];
75
+ for (const rule of resolverOrder) {
76
+ if (rule === "exact_keyword") {
77
+ const s = sorted.find((p) => p.type === "skill");
78
+ if (s) return s;
79
+ }
80
+ if (rule === "domain_rules") {
81
+ const w = sorted.find((p) => p.type === "workflow");
82
+ if (w) return w;
83
+ }
84
+ if (rule === "classification_default") return sorted[0];
85
+ }
86
+ return sorted[0];
87
+ }
88
+
89
+ function routeCapability(parsedIntent, matrix, registry, installed, matchKeywords) {
90
+ const rows = parsedIntent.segments.map((segment) => {
91
+ const classified = applyFileThresholdSignals(segment.classification, matrix);
92
+ const { skills, workflow, rulesHint } = collectSkills(segment.text, matrix, classified, matchKeywords);
93
+ return {
94
+ index: segment.index,
95
+ subtask: segment.text,
96
+ weight: classified.weight,
97
+ emoji: WEIGHT_EMOJI[classified.weight],
98
+ reason: classified.reasons.join("; "),
99
+ skills: skills.map((name) => ({ name, status: skillStatus(name, registry, installed) })),
100
+ workflow,
101
+ rulesHint,
102
+ taskType: classified.taskType || null
103
+ };
104
+ });
105
+
106
+ let overall = "light";
107
+ for (const r of rows) if (WEIGHT_RANK[r.weight] > WEIGHT_RANK[overall]) overall = r.weight;
108
+ if (rows.length >= 2 && WEIGHT_RANK[overall] < WEIGHT_RANK.medium) overall = matrix.classification.multi_item_bundle_min || "medium";
109
+
110
+ const showPlan = overall !== "light" || rows.length >= 2 || matchKeywords(parsedIntent.rawText, ["skill plan", "which skill", "plan_first"]);
111
+ const taskTypeResolved = rows.find((r) => r.taskType)?.taskType || null;
112
+ const providers = deriveProviders(taskTypeResolved, rows[0]?.skills?.map((s) => s.name) || [], rows[0]?.workflow || null);
113
+ const winner = pickProvider(providers, matrix?.resolver_order || []);
114
+
115
+ return { rows, overall, showPlan, taskTypeResolved, providers, winner, confidence: winner?.confidence_base || 0 };
116
+ }
117
+
118
+ module.exports = { routeCapability };
@@ -0,0 +1,87 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { resolveProviderActivation } = require("./providers/activation-policy");
4
+ const { createMemoryProviderStub } = require("./providers/memory-provider");
5
+ const { createGraphProviderStub } = require("./providers/graph-provider");
6
+ const { buildLocalContextPack } = require("./context-pack");
7
+ const { search: localMemorySearch } = require("./providers/local-memory-adapter");
8
+ const { query: localGraphQuery } = require("./providers/local-graph-adapter");
9
+
10
+ function doctorPassQuick(rootDir) {
11
+ const matrixPath = path.join(rootDir, "skill-routing-matrix.json");
12
+ const registryPath = path.join(rootDir, "registry.json");
13
+ if (!fs.existsSync(matrixPath) || !fs.existsSync(registryPath)) return false;
14
+ try {
15
+ JSON.parse(fs.readFileSync(matrixPath, "utf-8"));
16
+ JSON.parse(fs.readFileSync(registryPath, "utf-8"));
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ function localContextEnabled(featureFlags) {
24
+ if (featureFlags?.local_context === true) return true;
25
+ return process.env.CAPINT_LOCAL_CONTEXT === "1";
26
+ }
27
+
28
+ function resolveMemoryStrategy({ intent, routeResult, matrix, rootDir, featureFlags }) {
29
+ const requiresMemory = routeResult.taskTypeResolved?.requires_memory || "optional";
30
+ const highRiskCapabilities = new Set(["incident-response", "threat-modeling", "memory-retrieval"]);
31
+ const hasOverrideSignals = Boolean(intent?.overrides?.workflow || intent?.overrides?.skill);
32
+
33
+ let memory = requiresMemory;
34
+ if (requiresMemory === "none") memory = "none";
35
+ else if (requiresMemory === "required") memory = "required";
36
+ else if (highRiskCapabilities.has(routeResult?.taskTypeResolved?.capability) || hasOverrideSignals) {
37
+ memory = "required";
38
+ } else memory = "optional";
39
+
40
+ const doctorPass = rootDir ? doctorPassQuick(rootDir) : false;
41
+ const providerActivation = resolveProviderActivation({
42
+ memoryLevel: memory,
43
+ overallWeight: routeResult.overall,
44
+ capability: routeResult?.taskTypeResolved?.capability || null,
45
+ matrix,
46
+ doctorPass,
47
+ featureFlags
48
+ });
49
+
50
+ const providers = {
51
+ memory: providerActivation.memory.activated ? createMemoryProviderStub("memory-adapter") : createMemoryProviderStub("local"),
52
+ graph: providerActivation.graph.activated ? createGraphProviderStub("graph-adapter") : createGraphProviderStub("local")
53
+ };
54
+
55
+ const context_pack = buildLocalContextPack({
56
+ rootDir: rootDir || process.cwd(),
57
+ memoryLevel: memory,
58
+ providerActivated: providerActivation.anyActivated
59
+ });
60
+
61
+ let local_adapters = null;
62
+ if (localContextEnabled(featureFlags) && rootDir) {
63
+ local_adapters = {
64
+ memory: localMemorySearch(rootDir, intent?.rawText || ""),
65
+ graph: localGraphQuery(rootDir, routeResult?.taskTypeResolved?.capability || null)
66
+ };
67
+ }
68
+
69
+ return {
70
+ memory,
71
+ provider_activation: providerActivation,
72
+ providers_stub: { memory: providers.memory.name, graph: providers.graph.name },
73
+ context_pack,
74
+ local_adapters,
75
+ events: [
76
+ {
77
+ type: "route.intent_resolved",
78
+ capability: routeResult?.taskTypeResolved?.capability || null,
79
+ memory,
80
+ providers_activated: providerActivation.anyActivated,
81
+ local_context: Boolean(local_adapters)
82
+ }
83
+ ]
84
+ };
85
+ }
86
+
87
+ module.exports = { resolveMemoryStrategy, doctorPassQuick, localContextEnabled };
@@ -0,0 +1,39 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const LOCAL_SOURCES = [
5
+ { path: "AGENT.md", confidence: "verbatim", role: "session_contract" },
6
+ { path: "design.md", confidence: "verbatim", role: "project_boundaries" },
7
+ { path: "HANDOFF.md", confidence: "verbatim", role: "session_history" },
8
+ { path: ".capint/rules/core.md", confidence: "verbatim", role: "core_rules" }
9
+ ];
10
+
11
+ function buildLocalContextPack({ rootDir, memoryLevel, providerActivated }) {
12
+ if (memoryLevel === "none") {
13
+ return { pack_id: null, snippets: [], truncated: false, source: "none" };
14
+ }
15
+
16
+ const snippets = [];
17
+ for (const src of LOCAL_SOURCES) {
18
+ const abs = path.join(rootDir, src.path);
19
+ if (!fs.existsSync(abs)) continue;
20
+ const text = fs.readFileSync(abs, "utf-8");
21
+ const excerpt = text.slice(0, providerActivated ? 1200 : 600);
22
+ snippets.push({
23
+ text: excerpt,
24
+ source: src.path,
25
+ confidence: src.confidence,
26
+ role: src.role
27
+ });
28
+ }
29
+
30
+ return {
31
+ pack_id: `local-${Date.now()}`,
32
+ snippets,
33
+ truncated: snippets.some((s) => s.text.length >= 600),
34
+ source: providerActivated ? "local+provider_hint" : "local",
35
+ token_budget_hint: providerActivated ? 2000 : 800
36
+ };
37
+ }
38
+
39
+ module.exports = { buildLocalContextPack, LOCAL_SOURCES };
@@ -0,0 +1,71 @@
1
+ const EXECUTION_INTENT_KEYS = [
2
+ "intent",
3
+ "capability",
4
+ "resolution",
5
+ "memory",
6
+ "plan",
7
+ "confirm",
8
+ "confirm_question",
9
+ "confirm_options",
10
+ "confirm_default_option",
11
+ "fallback"
12
+ ];
13
+
14
+ const OPTIONAL_KEYS = [
15
+ "verification_required",
16
+ "verification_hints",
17
+ "route_explain",
18
+ "provider_activation",
19
+ "resolution_status",
20
+ "resolution_hint",
21
+ "local_adapters"
22
+ ];
23
+
24
+ function validateExecutionIntent(ei) {
25
+ const errors = [];
26
+ if (!ei || typeof ei !== "object") {
27
+ return { valid: false, errors: ["execution_intent must be an object"] };
28
+ }
29
+ for (const key of EXECUTION_INTENT_KEYS) {
30
+ if (!(key in ei)) errors.push(`missing required key: ${key}`);
31
+ }
32
+ if (typeof ei.intent !== "string") errors.push("intent must be string");
33
+ if (typeof ei.capability !== "string") errors.push("capability must be string");
34
+ if (typeof ei.resolution !== "string") errors.push("resolution must be string");
35
+ if (!["none", "optional", "required"].includes(ei.memory)) {
36
+ errors.push("memory must be none|optional|required");
37
+ }
38
+ if (!["auto", "confirm", "override"].includes(ei.plan)) {
39
+ errors.push("plan must be auto|confirm|override");
40
+ }
41
+ if (ei.confirm && typeof ei.confirm !== "object") errors.push("confirm must be object");
42
+ if (Array.isArray(ei.confirm_options)) {
43
+ for (const o of ei.confirm_options) {
44
+ if (!o.id || !o.label) errors.push("confirm_options entries need id and label");
45
+ }
46
+ }
47
+ if (typeof ei.verification_required === "boolean" && ei.verification_required) {
48
+ if (!Array.isArray(ei.verification_hints) || ei.verification_hints.length === 0) {
49
+ errors.push("verification_required=true requires verification_hints array");
50
+ }
51
+ }
52
+ return { valid: errors.length === 0, errors };
53
+ }
54
+
55
+ function stripVolatile(result) {
56
+ const copy = JSON.parse(JSON.stringify(result));
57
+ delete copy.installed_count;
58
+ delete copy.events;
59
+ delete copy.event_log;
60
+ if (copy.execution_intent?.route_explain) {
61
+ copy.execution_intent.route_explain = { ...copy.execution_intent.route_explain, ts: "<stripped>" };
62
+ }
63
+ return copy;
64
+ }
65
+
66
+ module.exports = {
67
+ EXECUTION_INTENT_KEYS,
68
+ OPTIONAL_KEYS,
69
+ validateExecutionIntent,
70
+ stripVolatile
71
+ };