kcp-harness 0.1.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 (46) hide show
  1. package/LICENSE +201 -0
  2. package/dist/audit.d.ts +73 -0
  3. package/dist/audit.js +142 -0
  4. package/dist/budget-ledger.d.ts +85 -0
  5. package/dist/budget-ledger.js +100 -0
  6. package/dist/classifier.d.ts +35 -0
  7. package/dist/classifier.js +177 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +177 -0
  10. package/dist/config.d.ts +65 -0
  11. package/dist/config.js +91 -0
  12. package/dist/downstream.d.ts +50 -0
  13. package/dist/downstream.js +167 -0
  14. package/dist/governor.d.ts +34 -0
  15. package/dist/governor.js +201 -0
  16. package/dist/index.d.ts +12 -0
  17. package/dist/index.js +21 -0
  18. package/dist/integrations/claude-code.d.ts +2 -0
  19. package/dist/integrations/claude-code.js +95 -0
  20. package/dist/integrations/cline.d.ts +2 -0
  21. package/dist/integrations/cline.js +70 -0
  22. package/dist/integrations/continue.d.ts +2 -0
  23. package/dist/integrations/continue.js +39 -0
  24. package/dist/integrations/copilot.d.ts +2 -0
  25. package/dist/integrations/copilot.js +69 -0
  26. package/dist/integrations/crush.d.ts +2 -0
  27. package/dist/integrations/crush.js +48 -0
  28. package/dist/integrations/cursor.d.ts +2 -0
  29. package/dist/integrations/cursor.js +65 -0
  30. package/dist/integrations/generate.d.ts +13 -0
  31. package/dist/integrations/generate.js +60 -0
  32. package/dist/integrations/openclaw.d.ts +2 -0
  33. package/dist/integrations/openclaw.js +67 -0
  34. package/dist/integrations/types.d.ts +51 -0
  35. package/dist/integrations/types.js +78 -0
  36. package/dist/integrations/windsurf.d.ts +2 -0
  37. package/dist/integrations/windsurf.js +67 -0
  38. package/dist/kcp-bridge.d.ts +2 -0
  39. package/dist/kcp-bridge.js +85 -0
  40. package/dist/proxy.d.ts +37 -0
  41. package/dist/proxy.js +394 -0
  42. package/dist/session.d.ts +50 -0
  43. package/dist/session.js +82 -0
  44. package/dist/temporal-watch.d.ts +63 -0
  45. package/dist/temporal-watch.js +147 -0
  46. package/package.json +56 -0
@@ -0,0 +1,147 @@
1
+ // Temporal watch — detect plan drift when time changes governance outcomes.
2
+ //
3
+ // The temporal watcher periodically re-evaluates approved plans against their
4
+ // manifests to detect when temporal gates (valid_from, valid_until, supersession)
5
+ // would produce a different plan than the one currently approved. When drift is
6
+ // detected, it emits a plan diff and optionally invalidates the stale plan.
7
+ //
8
+ // This is the "temporal governance" layer: it ensures that approved plans don't
9
+ // outlive their temporal validity. A plan approved at 09:00 might become stale
10
+ // at midnight when a superseding unit activates.
11
+ //
12
+ // Usage:
13
+ // 1. Register plans with the watcher (automatic via governor)
14
+ // 2. Call `check()` to evaluate all plans against current time
15
+ // 3. Drifted plans emit audit events and are optionally invalidated
16
+ //
17
+ // The watcher is pure: it re-runs the kcp-agent planner with the current
18
+ // date and diffs against the stored plan. No mutation unless `invalidate` is set.
19
+ import { planTree, plans, diffPlans, } from "kcp-agent";
20
+ /** Temporal plan watcher. */
21
+ export class TemporalWatch {
22
+ watched = new Map();
23
+ /** Register a plan for temporal watching. */
24
+ register(manifest, task, plan, followOptions) {
25
+ this.watched.set(manifest, {
26
+ manifest,
27
+ task,
28
+ plan,
29
+ followOptions,
30
+ registeredAt: new Date().toISOString(),
31
+ });
32
+ }
33
+ /** Remove a plan from watching (e.g., after invalidation). */
34
+ unregister(manifest) {
35
+ this.watched.delete(manifest);
36
+ }
37
+ /** Check all watched plans for temporal drift. */
38
+ async check() {
39
+ const drifted = [];
40
+ const errors = [];
41
+ let stable = 0;
42
+ for (const [manifest, watched] of this.watched) {
43
+ try {
44
+ const result = await this.checkOne(watched);
45
+ watched.lastChecked = result.checkedAt;
46
+ if (result.drifted) {
47
+ drifted.push(result);
48
+ }
49
+ else {
50
+ stable++;
51
+ }
52
+ }
53
+ catch (e) {
54
+ const msg = e instanceof Error ? e.message : String(e);
55
+ errors.push({ manifest, error: msg });
56
+ }
57
+ }
58
+ return { checked: this.watched.size, drifted, stable, errors };
59
+ }
60
+ /** Check a single plan for temporal drift. */
61
+ async checkOne(watched) {
62
+ const now = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
63
+ // Re-plan with current date
64
+ const freshOptions = {
65
+ ...watched.followOptions,
66
+ planOptions: {
67
+ ...watched.followOptions.planOptions,
68
+ asOf: now, // Force current date
69
+ },
70
+ };
71
+ const tree = await planTree(watched.manifest, watched.task, freshOptions);
72
+ if (tree.error) {
73
+ throw new Error(`manifest error: ${tree.error}`);
74
+ }
75
+ const freshPlans = Array.from(plans(tree));
76
+ const freshPlan = freshPlans[0];
77
+ if (!freshPlan) {
78
+ throw new Error("re-plan returned no plan");
79
+ }
80
+ // Diff against the stored plan
81
+ const diff = diffPlans(watched.plan, freshPlan);
82
+ const checkedAt = new Date().toISOString();
83
+ if (diff.identical) {
84
+ return {
85
+ manifest: watched.manifest,
86
+ task: watched.task,
87
+ drifted: false,
88
+ summary: `plan is stable (checked at ${now})`,
89
+ checkedAt,
90
+ };
91
+ }
92
+ // Build a human-readable summary of what changed
93
+ const summary = summarizeDrift(diff);
94
+ return {
95
+ manifest: watched.manifest,
96
+ task: watched.task,
97
+ drifted: true,
98
+ diff,
99
+ newPlan: freshPlan,
100
+ summary,
101
+ checkedAt,
102
+ };
103
+ }
104
+ /** Get all watched plans. */
105
+ getWatched() {
106
+ return this.watched;
107
+ }
108
+ /** Get a watched plan by manifest. */
109
+ get(manifest) {
110
+ return this.watched.get(manifest);
111
+ }
112
+ }
113
+ /** Build a human-readable summary of plan drift. */
114
+ function summarizeDrift(diff) {
115
+ const parts = [];
116
+ if (diff.moves.length > 0) {
117
+ const sel2skip = diff.moves.filter((m) => m.direction === "selected_to_skipped");
118
+ const skip2sel = diff.moves.filter((m) => m.direction === "skipped_to_selected");
119
+ if (sel2skip.length > 0) {
120
+ parts.push(`${sel2skip.length} unit(s) dropped: ${sel2skip.map((m) => m.id).join(", ")}`);
121
+ }
122
+ if (skip2sel.length > 0) {
123
+ parts.push(`${skip2sel.length} unit(s) activated: ${skip2sel.map((m) => m.id).join(", ")}`);
124
+ }
125
+ }
126
+ if (diff.scoreChanges.length > 0) {
127
+ parts.push(`${diff.scoreChanges.length} score change(s)`);
128
+ }
129
+ if (diff.presence.length > 0) {
130
+ const added = diff.presence.filter((p) => p.side === "b_only");
131
+ const removed = diff.presence.filter((p) => p.side === "a_only");
132
+ if (added.length > 0)
133
+ parts.push(`${added.length} new unit(s)`);
134
+ if (removed.length > 0)
135
+ parts.push(`${removed.length} removed unit(s)`);
136
+ }
137
+ if (diff.reasonChanges.length > 0) {
138
+ parts.push(`${diff.reasonChanges.length} reason change(s)`);
139
+ }
140
+ if (diff.budgetShifts.length > 0) {
141
+ parts.push(`${diff.budgetShifts.length} budget shift(s)`);
142
+ }
143
+ const temporal = diff.a.asOf !== diff.b.asOf
144
+ ? ` (${diff.a.asOf} → ${diff.b.asOf})`
145
+ : "";
146
+ return `temporal drift detected${temporal}: ${parts.join("; ") || "structural change"}`;
147
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "kcp-harness",
3
+ "version": "0.1.0",
4
+ "description": "KCP Compliance Harness — MCP proxy that enforces deterministic knowledge governance for any agent",
5
+ "type": "module",
6
+ "bin": {
7
+ "kcp-harness": "dist/cli.js"
8
+ },
9
+ "exports": {
10
+ ".": "./dist/index.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "docs:dev": "vitepress dev docs",
21
+ "docs:build": "vitepress build docs",
22
+ "docs:preview": "vitepress preview docs"
23
+ },
24
+ "dependencies": {
25
+ "js-yaml": "^4.1.0",
26
+ "kcp-agent": "^0.11.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/js-yaml": "^4.0.9",
30
+ "@types/node": "^22.0.0",
31
+ "typescript": "^5.6.0",
32
+ "vitepress": "^1.6.4",
33
+ "vitest": "^2.1.0"
34
+ },
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
38
+ "keywords": [
39
+ "kcp",
40
+ "knowledge-context-protocol",
41
+ "mcp",
42
+ "compliance",
43
+ "harness",
44
+ "governance",
45
+ "agent",
46
+ "ai",
47
+ "audit"
48
+ ],
49
+ "homepage": "https://cantara.github.io/kcp-harness/",
50
+ "author": "eXOReaction AS",
51
+ "license": "Apache-2.0",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/Cantara/kcp-harness.git"
55
+ }
56
+ }