oxe-cc 1.0.0 → 1.2.1

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 (207) hide show
  1. package/.cursor/commands/oxe-ask.md +1 -1
  2. package/.cursor/commands/oxe-capabilities.md +1 -1
  3. package/.cursor/commands/oxe-checkpoint.md +1 -1
  4. package/.cursor/commands/oxe-compact.md +1 -1
  5. package/.cursor/commands/oxe-dashboard.md +1 -1
  6. package/.cursor/commands/oxe-debug.md +1 -1
  7. package/.cursor/commands/oxe-discuss.md +1 -1
  8. package/.cursor/commands/oxe-execute.md +2 -2
  9. package/.cursor/commands/oxe-forensics.md +1 -1
  10. package/.cursor/commands/oxe-help.md +1 -1
  11. package/.cursor/commands/oxe-loop.md +1 -1
  12. package/.cursor/commands/oxe-milestone.md +1 -1
  13. package/.cursor/commands/oxe-next.md +1 -1
  14. package/.cursor/commands/oxe-obs.md +1 -1
  15. package/.cursor/commands/oxe-plan-agent.md +1 -1
  16. package/.cursor/commands/oxe-plan.md +1 -1
  17. package/.cursor/commands/oxe-project.md +1 -1
  18. package/.cursor/commands/oxe-quick.md +1 -1
  19. package/.cursor/commands/oxe-research.md +1 -1
  20. package/.cursor/commands/oxe-retro.md +1 -1
  21. package/.cursor/commands/oxe-review-pr.md +1 -1
  22. package/.cursor/commands/oxe-route.md +1 -1
  23. package/.cursor/commands/oxe-scan.md +1 -1
  24. package/.cursor/commands/oxe-security.md +1 -1
  25. package/.cursor/commands/oxe-session.md +2 -2
  26. package/.cursor/commands/oxe-ship.md +45 -0
  27. package/.cursor/commands/oxe-skill.md +1 -1
  28. package/.cursor/commands/oxe-spec.md +1 -1
  29. package/.cursor/commands/oxe-ui-review.md +1 -1
  30. package/.cursor/commands/oxe-ui-spec.md +1 -1
  31. package/.cursor/commands/oxe-update.md +1 -1
  32. package/.cursor/commands/oxe-validate-gaps.md +1 -1
  33. package/.cursor/commands/oxe-verify.md +1 -1
  34. package/.cursor/commands/oxe-workstream.md +1 -1
  35. package/.cursor/commands/oxe.md +4 -4
  36. package/.github/copilot-instructions.md +91 -1
  37. package/.github/prompts/oxe-ask.prompt.md +1 -1
  38. package/.github/prompts/oxe-capabilities.prompt.md +1 -1
  39. package/.github/prompts/oxe-checkpoint.prompt.md +1 -1
  40. package/.github/prompts/oxe-compact.prompt.md +1 -1
  41. package/.github/prompts/oxe-dashboard.prompt.md +1 -1
  42. package/.github/prompts/oxe-debug.prompt.md +1 -1
  43. package/.github/prompts/oxe-discuss.prompt.md +1 -1
  44. package/.github/prompts/oxe-execute.prompt.md +2 -2
  45. package/.github/prompts/oxe-forensics.prompt.md +1 -1
  46. package/.github/prompts/oxe-help.prompt.md +1 -1
  47. package/.github/prompts/oxe-loop.prompt.md +1 -1
  48. package/.github/prompts/oxe-milestone.prompt.md +1 -1
  49. package/.github/prompts/oxe-next.prompt.md +1 -1
  50. package/.github/prompts/oxe-obs.prompt.md +1 -1
  51. package/.github/prompts/oxe-plan-agent.prompt.md +1 -1
  52. package/.github/prompts/oxe-plan.prompt.md +1 -1
  53. package/.github/prompts/oxe-project.prompt.md +1 -1
  54. package/.github/prompts/oxe-quick.prompt.md +1 -1
  55. package/.github/prompts/oxe-research.prompt.md +1 -1
  56. package/.github/prompts/oxe-retro.prompt.md +1 -1
  57. package/.github/prompts/oxe-review-pr.prompt.md +1 -1
  58. package/.github/prompts/oxe-route.prompt.md +1 -1
  59. package/.github/prompts/oxe-scan.prompt.md +1 -1
  60. package/.github/prompts/oxe-security.prompt.md +1 -1
  61. package/.github/prompts/oxe-session.prompt.md +2 -2
  62. package/.github/prompts/oxe-ship.prompt.md +45 -0
  63. package/.github/prompts/oxe-skill.prompt.md +1 -1
  64. package/.github/prompts/oxe-spec.prompt.md +1 -1
  65. package/.github/prompts/oxe-ui-review.prompt.md +1 -1
  66. package/.github/prompts/oxe-ui-spec.prompt.md +1 -1
  67. package/.github/prompts/oxe-update.prompt.md +1 -1
  68. package/.github/prompts/oxe-validate-gaps.prompt.md +1 -1
  69. package/.github/prompts/oxe-verify.prompt.md +1 -1
  70. package/.github/prompts/oxe-workstream.prompt.md +1 -1
  71. package/.github/prompts/oxe.prompt.md +3 -3
  72. package/AGENTS.md +43 -28
  73. package/CHANGELOG.md +158 -0
  74. package/README.md +72 -50
  75. package/bin/banner.txt +1 -1
  76. package/bin/lib/oxe-project-health.cjs +1 -1
  77. package/commands/oxe/ask.md +5 -1
  78. package/commands/oxe/checkpoint.md +1 -1
  79. package/commands/oxe/compact.md +1 -1
  80. package/commands/oxe/debug.md +1 -1
  81. package/commands/oxe/execute.md +2 -2
  82. package/commands/oxe/forensics.md +1 -1
  83. package/commands/oxe/loop.md +1 -1
  84. package/commands/oxe/milestone.md +1 -1
  85. package/commands/oxe/next.md +1 -1
  86. package/commands/oxe/obs.md +1 -1
  87. package/commands/oxe/oxe.md +3 -3
  88. package/commands/oxe/project.md +1 -1
  89. package/commands/oxe/research.md +1 -1
  90. package/commands/oxe/retro.md +1 -1
  91. package/commands/oxe/review-pr.md +1 -1
  92. package/commands/oxe/route.md +1 -1
  93. package/commands/oxe/scan.md +1 -1
  94. package/commands/oxe/security.md +1 -1
  95. package/commands/oxe/session.md +2 -2
  96. package/commands/oxe/ship.md +49 -0
  97. package/commands/oxe/spec.md +2 -2
  98. package/commands/oxe/ui-review.md +1 -1
  99. package/commands/oxe/ui-spec.md +1 -1
  100. package/commands/oxe/validate-gaps.md +1 -1
  101. package/commands/oxe/verify.md +2 -2
  102. package/commands/oxe/workstream.md +1 -1
  103. package/lib/runtime/audit/audit-trail.d.ts +71 -0
  104. package/lib/runtime/audit/audit-trail.js +154 -0
  105. package/lib/runtime/audit/index.d.ts +2 -0
  106. package/lib/runtime/audit/index.js +18 -0
  107. package/lib/runtime/audit/policy-pack.d.ts +15 -0
  108. package/lib/runtime/audit/policy-pack.js +57 -0
  109. package/lib/runtime/context/context-pack-builder.d.ts +15 -0
  110. package/lib/runtime/context/context-pack-builder.js +42 -0
  111. package/lib/runtime/context/context-pack-store.d.ts +38 -0
  112. package/lib/runtime/context/context-pack-store.js +142 -0
  113. package/lib/runtime/context/context-profiles.d.ts +11 -0
  114. package/lib/runtime/context/context-profiles.js +51 -0
  115. package/lib/runtime/context/index.d.ts +2 -0
  116. package/lib/runtime/context/index.js +2 -0
  117. package/lib/runtime/decision/decision-engine.d.ts +43 -0
  118. package/lib/runtime/decision/decision-engine.js +127 -0
  119. package/lib/runtime/decision/decision-memo.d.ts +53 -0
  120. package/lib/runtime/decision/decision-memo.js +173 -0
  121. package/lib/runtime/decision/index.d.ts +2 -0
  122. package/lib/runtime/decision/index.js +18 -0
  123. package/lib/runtime/delivery/index.d.ts +1 -0
  124. package/lib/runtime/delivery/index.js +1 -0
  125. package/lib/runtime/delivery/promotion-pipeline.d.ts +39 -0
  126. package/lib/runtime/delivery/promotion-pipeline.js +127 -0
  127. package/lib/runtime/index.d.ts +3 -0
  128. package/lib/runtime/index.js +4 -0
  129. package/lib/runtime/plugins/capability-matrix.d.ts +20 -0
  130. package/lib/runtime/plugins/capability-matrix.js +59 -0
  131. package/lib/runtime/plugins/index.d.ts +2 -0
  132. package/lib/runtime/plugins/index.js +2 -0
  133. package/lib/runtime/plugins/plugin-manifest.d.ts +22 -0
  134. package/lib/runtime/plugins/plugin-manifest.js +91 -0
  135. package/lib/runtime/plugins/plugin-registry.js +5 -0
  136. package/lib/runtime/policy/policy-engine.d.ts +28 -1
  137. package/lib/runtime/policy/policy-engine.js +96 -5
  138. package/lib/runtime/reducers/run-state-reducer.d.ts +26 -0
  139. package/lib/runtime/reducers/run-state-reducer.js +117 -1
  140. package/lib/runtime/scheduler/agent-registry.d.ts +44 -0
  141. package/lib/runtime/scheduler/agent-registry.js +96 -0
  142. package/lib/runtime/scheduler/agent-roles.d.ts +54 -0
  143. package/lib/runtime/scheduler/agent-roles.js +62 -0
  144. package/lib/runtime/scheduler/index.d.ts +3 -0
  145. package/lib/runtime/scheduler/index.js +3 -0
  146. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +2 -0
  147. package/lib/runtime/scheduler/multi-agent-coordinator.js +91 -4
  148. package/lib/runtime/scheduler/run-journal.d.ts +18 -0
  149. package/lib/runtime/scheduler/run-journal.js +54 -0
  150. package/lib/runtime/scheduler/scheduler.d.ts +11 -1
  151. package/lib/runtime/scheduler/scheduler.js +135 -7
  152. package/lib/runtime/verification/index.d.ts +1 -0
  153. package/lib/runtime/verification/index.js +1 -0
  154. package/lib/runtime/verification/verification-manifest.d.ts +58 -0
  155. package/lib/runtime/verification/verification-manifest.js +129 -0
  156. package/oxe/workflows/ask.md +4 -0
  157. package/oxe/workflows/checkpoint.md +14 -10
  158. package/oxe/workflows/debug.md +19 -15
  159. package/oxe/workflows/execute.md +30 -2
  160. package/oxe/workflows/forensics.md +13 -9
  161. package/oxe/workflows/help.md +97 -49
  162. package/oxe/workflows/loop.md +17 -13
  163. package/oxe/workflows/obs.md +4 -0
  164. package/oxe/workflows/oxe.md +64 -31
  165. package/oxe/workflows/project.md +6 -1
  166. package/oxe/workflows/references/workflow-runtime-contracts.json +23 -0
  167. package/oxe/workflows/research.md +32 -28
  168. package/oxe/workflows/retro.md +4 -0
  169. package/oxe/workflows/review-pr.md +15 -11
  170. package/oxe/workflows/scan.md +4 -0
  171. package/oxe/workflows/security.md +14 -10
  172. package/oxe/workflows/session.md +17 -1
  173. package/oxe/workflows/ship.md +142 -0
  174. package/oxe/workflows/spec.md +15 -0
  175. package/oxe/workflows/ui-review.md +20 -16
  176. package/oxe/workflows/ui-spec.md +7 -3
  177. package/oxe/workflows/validate-gaps.md +13 -9
  178. package/oxe/workflows/verify.md +42 -3
  179. package/package.json +1 -1
  180. package/packages/runtime/src/audit/audit-trail.ts +243 -0
  181. package/packages/runtime/src/audit/index.ts +2 -0
  182. package/packages/runtime/src/audit/policy-pack.ts +62 -0
  183. package/packages/runtime/src/context/context-pack-builder.ts +66 -0
  184. package/packages/runtime/src/context/context-pack-store.ts +197 -0
  185. package/packages/runtime/src/context/context-profiles.ts +60 -0
  186. package/packages/runtime/src/context/index.ts +2 -0
  187. package/packages/runtime/src/decision/decision-engine.ts +174 -0
  188. package/packages/runtime/src/decision/decision-memo.ts +211 -0
  189. package/packages/runtime/src/decision/index.ts +2 -0
  190. package/packages/runtime/src/delivery/index.ts +1 -0
  191. package/packages/runtime/src/delivery/promotion-pipeline.ts +180 -0
  192. package/packages/runtime/src/index.ts +5 -0
  193. package/packages/runtime/src/plugins/capability-matrix.ts +83 -0
  194. package/packages/runtime/src/plugins/index.ts +2 -0
  195. package/packages/runtime/src/plugins/plugin-manifest.ts +113 -0
  196. package/packages/runtime/src/plugins/plugin-registry.ts +5 -0
  197. package/packages/runtime/src/policy/policy-engine.ts +138 -7
  198. package/packages/runtime/src/reducers/run-state-reducer.ts +143 -1
  199. package/packages/runtime/src/scheduler/agent-registry.ts +132 -0
  200. package/packages/runtime/src/scheduler/agent-roles.ts +109 -0
  201. package/packages/runtime/src/scheduler/index.ts +3 -0
  202. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +106 -4
  203. package/packages/runtime/src/scheduler/run-journal.ts +62 -0
  204. package/packages/runtime/src/scheduler/scheduler.ts +168 -8
  205. package/packages/runtime/src/verification/index.ts +1 -0
  206. package/packages/runtime/src/verification/verification-manifest.ts +192 -0
  207. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
@@ -0,0 +1,243 @@
1
+ import crypto from 'crypto';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+
5
+ // ─── RemoteAuditSink ─────────────────────────────────────────────────────────
6
+
7
+ export interface AuditQueryFilter {
8
+ action?: AuditAction;
9
+ severity?: AuditSeverity;
10
+ runId?: string;
11
+ since?: string;
12
+ }
13
+
14
+ export interface RemoteAuditSink {
15
+ write(entry: AuditEntry): Promise<void>;
16
+ query(filter: AuditQueryFilter): Promise<AuditEntry[]>;
17
+ }
18
+
19
+ // ─── AuditMetrics ────────────────────────────────────────────────────────────
20
+
21
+ export interface AuditMetrics {
22
+ total_entries: number;
23
+ critical_count: number;
24
+ warn_count: number;
25
+ by_action: Partial<Record<AuditAction, number>>;
26
+ actors: string[];
27
+ oldest: string | null;
28
+ newest: string | null;
29
+ }
30
+
31
+ export type AuditAction =
32
+ | 'run_started'
33
+ | 'run_completed'
34
+ | 'run_paused'
35
+ | 'run_recovered'
36
+ | 'gate_requested'
37
+ | 'gate_resolved'
38
+ | 'policy_denied'
39
+ | 'plugin_registered'
40
+ | 'plugin_invoked'
41
+ | 'secret_accessed'
42
+ | 'infra_mutation'
43
+ | 'pr_created'
44
+ | 'merge_approved'
45
+ | 'merge_blocked';
46
+
47
+ export type AuditSeverity = 'info' | 'warn' | 'critical';
48
+
49
+ export interface AuditEntry {
50
+ audit_id: string;
51
+ action: AuditAction;
52
+ severity: AuditSeverity;
53
+ run_id: string | null;
54
+ work_item_id: string | null;
55
+ actor: string;
56
+ resource: string | null;
57
+ detail: Record<string, unknown>;
58
+ timestamp: string;
59
+ }
60
+
61
+ export interface RunQuota {
62
+ run_id: string;
63
+ max_work_items: number;
64
+ max_mutations: number;
65
+ max_retries_total: number;
66
+ consumed_work_items: number;
67
+ consumed_mutations: number;
68
+ consumed_retries: number;
69
+ }
70
+
71
+ export interface QuotaViolation {
72
+ quota_type: 'work_items' | 'mutations' | 'retries';
73
+ limit: number;
74
+ consumed: number;
75
+ }
76
+
77
+ const ACTION_SEVERITY: Record<AuditAction, AuditSeverity> = {
78
+ run_started: 'info',
79
+ run_completed: 'info',
80
+ run_paused: 'info',
81
+ run_recovered: 'warn',
82
+ gate_requested: 'warn',
83
+ gate_resolved: 'info',
84
+ policy_denied: 'warn',
85
+ plugin_registered: 'info',
86
+ plugin_invoked: 'info',
87
+ secret_accessed: 'critical',
88
+ infra_mutation: 'critical',
89
+ pr_created: 'info',
90
+ merge_approved: 'warn',
91
+ merge_blocked: 'warn',
92
+ };
93
+
94
+ export class AuditTrail {
95
+ constructor(
96
+ private readonly projectRoot: string,
97
+ private readonly remoteSink?: RemoteAuditSink
98
+ ) {}
99
+
100
+ record(
101
+ action: AuditAction,
102
+ actor: string,
103
+ options: {
104
+ runId?: string;
105
+ workItemId?: string;
106
+ resource?: string;
107
+ detail?: Record<string, unknown>;
108
+ } = {}
109
+ ): AuditEntry {
110
+ const entry: AuditEntry = {
111
+ audit_id: `aud-${crypto.randomBytes(4).toString('hex')}`,
112
+ action,
113
+ severity: ACTION_SEVERITY[action],
114
+ run_id: options.runId ?? null,
115
+ work_item_id: options.workItemId ?? null,
116
+ actor,
117
+ resource: options.resource ?? null,
118
+ detail: options.detail ?? {},
119
+ timestamp: new Date().toISOString(),
120
+ };
121
+
122
+ this.append(entry);
123
+ return entry;
124
+ }
125
+
126
+ query(filter: {
127
+ action?: AuditAction;
128
+ severity?: AuditSeverity;
129
+ runId?: string;
130
+ since?: string;
131
+ } = {}): AuditEntry[] {
132
+ return this.load().filter((e) => {
133
+ if (filter.action && e.action !== filter.action) return false;
134
+ if (filter.severity && e.severity !== filter.severity) return false;
135
+ if (filter.runId && e.run_id !== filter.runId) return false;
136
+ if (filter.since && e.timestamp < filter.since) return false;
137
+ return true;
138
+ });
139
+ }
140
+
141
+ critical(): AuditEntry[] {
142
+ return this.query({ severity: 'critical' });
143
+ }
144
+
145
+ metrics(): AuditMetrics {
146
+ const entries = this.load();
147
+ const by_action: Partial<Record<AuditAction, number>> = {};
148
+ const actorSet = new Set<string>();
149
+ let oldest: string | null = null;
150
+ let newest: string | null = null;
151
+ let critical_count = 0;
152
+ let warn_count = 0;
153
+
154
+ for (const e of entries) {
155
+ by_action[e.action] = (by_action[e.action] ?? 0) + 1;
156
+ actorSet.add(e.actor);
157
+ if (e.severity === 'critical') critical_count++;
158
+ if (e.severity === 'warn') warn_count++;
159
+ if (!oldest || e.timestamp < oldest) oldest = e.timestamp;
160
+ if (!newest || e.timestamp > newest) newest = e.timestamp;
161
+ }
162
+
163
+ return {
164
+ total_entries: entries.length,
165
+ critical_count,
166
+ warn_count,
167
+ by_action,
168
+ actors: [...actorSet],
169
+ oldest,
170
+ newest,
171
+ };
172
+ }
173
+
174
+ private append(entry: AuditEntry): void {
175
+ const p = this.trailPath();
176
+ fs.mkdirSync(path.dirname(p), { recursive: true });
177
+ fs.appendFileSync(p, JSON.stringify(entry) + '\n', 'utf8');
178
+ // Fire-and-forget remote sink (failures are non-fatal)
179
+ if (this.remoteSink) {
180
+ this.remoteSink.write(entry).catch(() => {});
181
+ }
182
+ }
183
+
184
+ private load(): AuditEntry[] {
185
+ const p = this.trailPath();
186
+ if (!fs.existsSync(p)) return [];
187
+ try {
188
+ return fs
189
+ .readFileSync(p, 'utf8')
190
+ .split('\n')
191
+ .filter(Boolean)
192
+ .map((line) => JSON.parse(line) as AuditEntry);
193
+ } catch {
194
+ return [];
195
+ }
196
+ }
197
+
198
+ private trailPath(): string {
199
+ return path.join(this.projectRoot, '.oxe', 'AUDIT-TRAIL.ndjson');
200
+ }
201
+ }
202
+
203
+ // ─── RunQuota ─────────────────────────────────────────────────────────────────
204
+
205
+ export function createQuota(
206
+ runId: string,
207
+ limits: Partial<Omit<RunQuota, 'run_id' | 'consumed_work_items' | 'consumed_mutations' | 'consumed_retries'>> = {}
208
+ ): RunQuota {
209
+ return {
210
+ run_id: runId,
211
+ max_work_items: limits.max_work_items ?? Infinity,
212
+ max_mutations: limits.max_mutations ?? Infinity,
213
+ max_retries_total: limits.max_retries_total ?? Infinity,
214
+ consumed_work_items: 0,
215
+ consumed_mutations: 0,
216
+ consumed_retries: 0,
217
+ };
218
+ }
219
+
220
+ export function checkQuota(quota: RunQuota): QuotaViolation | null {
221
+ if (quota.consumed_work_items > quota.max_work_items) {
222
+ return { quota_type: 'work_items', limit: quota.max_work_items, consumed: quota.consumed_work_items };
223
+ }
224
+ if (quota.consumed_mutations > quota.max_mutations) {
225
+ return { quota_type: 'mutations', limit: quota.max_mutations, consumed: quota.consumed_mutations };
226
+ }
227
+ if (quota.consumed_retries > quota.max_retries_total) {
228
+ return { quota_type: 'retries', limit: quota.max_retries_total, consumed: quota.consumed_retries };
229
+ }
230
+ return null;
231
+ }
232
+
233
+ export function consumeQuota(
234
+ quota: RunQuota,
235
+ type: QuotaViolation['quota_type'],
236
+ amount = 1
237
+ ): RunQuota {
238
+ switch (type) {
239
+ case 'work_items': return { ...quota, consumed_work_items: quota.consumed_work_items + amount };
240
+ case 'mutations': return { ...quota, consumed_mutations: quota.consumed_mutations + amount };
241
+ case 'retries': return { ...quota, consumed_retries: quota.consumed_retries + amount };
242
+ }
243
+ }
@@ -0,0 +1,2 @@
1
+ export * from './audit-trail';
2
+ export * from './policy-pack';
@@ -0,0 +1,62 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import type { PolicyRule, EnvironmentGuardrail } from '../policy/policy-engine';
4
+ import { PolicyEngine } from '../policy/policy-engine';
5
+
6
+ export interface PolicyPack {
7
+ pack_id: string;
8
+ org_id: string;
9
+ name: string;
10
+ version: string;
11
+ policies: PolicyRule[];
12
+ guardrail: EnvironmentGuardrail;
13
+ created_at: string;
14
+ }
15
+
16
+ function packDir(projectRoot: string): string {
17
+ return path.join(projectRoot, '.oxe', 'policy-packs');
18
+ }
19
+
20
+ function packFilePath(projectRoot: string, packId: string): string {
21
+ return path.join(packDir(projectRoot), `${packId}.json`);
22
+ }
23
+
24
+ export function savePolicyPack(projectRoot: string, pack: PolicyPack): void {
25
+ const dir = packDir(projectRoot);
26
+ fs.mkdirSync(dir, { recursive: true });
27
+ fs.writeFileSync(packFilePath(projectRoot, pack.pack_id), JSON.stringify(pack, null, 2), 'utf8');
28
+ }
29
+
30
+ export function loadPolicyPack(projectRoot: string, packId: string): PolicyPack | null {
31
+ const p = packFilePath(projectRoot, packId);
32
+ if (!fs.existsSync(p)) return null;
33
+ try {
34
+ return JSON.parse(fs.readFileSync(p, 'utf8')) as PolicyPack;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ export function listPolicyPacks(projectRoot: string): PolicyPack[] {
41
+ const dir = packDir(projectRoot);
42
+ if (!fs.existsSync(dir)) return [];
43
+ return fs
44
+ .readdirSync(dir)
45
+ .filter((f) => f.endsWith('.json'))
46
+ .map((f) => {
47
+ try {
48
+ return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')) as PolicyPack;
49
+ } catch {
50
+ return null;
51
+ }
52
+ })
53
+ .filter((p): p is PolicyPack => p !== null);
54
+ }
55
+
56
+ export function applyPolicyPack(engine: PolicyEngine, pack: PolicyPack): PolicyEngine {
57
+ let result = engine.withGuardrail(pack.guardrail);
58
+ for (const rule of pack.policies) {
59
+ result = result.withRule(rule);
60
+ }
61
+ return result;
62
+ }
@@ -1,6 +1,8 @@
1
1
  import type { WorkItem } from '../models/work-item';
2
2
  import type { Evidence } from '../models/evidence';
3
3
  import type { RunState } from '../reducers/run-state-reducer';
4
+ import type { ContextProfile } from './context-profiles';
5
+ import type { AutonomyTier } from '../policy/policy-engine';
4
6
 
5
7
  export interface ContextArtifact {
6
8
  id: string;
@@ -32,6 +34,50 @@ export interface ContextPack {
32
34
  built_at: string;
33
35
  }
34
36
 
37
+ // ─── Quality scoring ─────────────────────────────────────────────────────────
38
+
39
+ export interface ContextQualityScore {
40
+ completeness: number;
41
+ relevance_mean: number;
42
+ redundancy: number;
43
+ recency_score: number;
44
+ overall: number;
45
+ }
46
+
47
+ export function scorePackQuality(pack: ContextPack, profile: ContextProfile): ContextQualityScore {
48
+ const expectedKinds = Object.keys(profile.artifact_kind_weights) as ContextArtifact['kind'][];
49
+ const presentKinds = new Set(pack.artifacts.map((a) => a.kind));
50
+ const completeness = expectedKinds.length > 0
51
+ ? expectedKinds.filter((k) => presentKinds.has(k)).length / expectedKinds.length
52
+ : 0;
53
+
54
+ const relevance_mean = pack.artifacts.length > 0
55
+ ? pack.artifacts.reduce((sum, a) => sum + a.relevanceScore, 0) / pack.artifacts.length
56
+ : 0;
57
+
58
+ const total = pack.total_artifacts_considered;
59
+ const redundancy = total > 0 ? 1 - (pack.artifacts.length / total) : 0;
60
+
61
+ const ageMs = Date.now() - new Date(pack.built_at).getTime();
62
+ const maxAgeMs = 24 * 60 * 60 * 1000;
63
+ const recency_score = Math.max(0, 1 - ageMs / maxAgeMs);
64
+
65
+ const overall = Math.min(1,
66
+ 0.3 * completeness +
67
+ 0.35 * relevance_mean +
68
+ 0.1 * (1 - redundancy) +
69
+ 0.25 * recency_score
70
+ );
71
+
72
+ return {
73
+ completeness: Math.round(completeness * 100) / 100,
74
+ relevance_mean: Math.round(relevance_mean * 100) / 100,
75
+ redundancy: Math.round(redundancy * 100) / 100,
76
+ recency_score: Math.round(recency_score * 100) / 100,
77
+ overall: Math.round(overall * 100) / 100,
78
+ };
79
+ }
80
+
35
81
  // ─── Relevance scoring ────────────────────────────────────────────────────────
36
82
 
37
83
  function scoreEvidenceRelevance(evidence: Evidence, workItem: WorkItem): number {
@@ -190,4 +236,24 @@ export class ContextPackBuilder {
190
236
  buildLightweight(workItem: WorkItem, state: RunState, lessons: LessonMetric[]): ContextPack {
191
237
  return this.build(workItem, state, [], new Map(), lessons);
192
238
  }
239
+
240
+ /**
241
+ * Filter artifacts to those whose path-like tags are within mutation_scope.
242
+ * L0/L1 tiers apply the filter; L2/L3 skip it (full access).
243
+ */
244
+ filterByMutationScope(
245
+ artifacts: ContextArtifact[],
246
+ mutationScope: string[],
247
+ autonomyTier: AutonomyTier
248
+ ): ContextArtifact[] {
249
+ if (autonomyTier === 'L2' || autonomyTier === 'L3') return artifacts;
250
+ const scope = mutationScope.map((s) => s.toLowerCase());
251
+ return artifacts.filter((a) => {
252
+ const pathTags = a.tags.filter((t) => t.includes('/') || t.includes('\\'));
253
+ if (pathTags.length === 0) return true;
254
+ return pathTags.some((tag) =>
255
+ scope.some((s) => tag.toLowerCase().includes(s) || s.includes(tag.toLowerCase()))
256
+ );
257
+ });
258
+ }
193
259
  }
@@ -0,0 +1,197 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import type { ContextPack, ContextArtifact, ContextQualityScore } from './context-pack-builder';
4
+
5
+ export interface ContextPackMeta {
6
+ pack_id: string;
7
+ work_item_id: string;
8
+ run_id: string;
9
+ built_at: string;
10
+ artifact_count: number;
11
+ estimated_tokens: number;
12
+ stale: boolean;
13
+ stale_reason: string | null;
14
+ }
15
+
16
+ export interface ContextPackDiff {
17
+ added: string[];
18
+ removed: string[];
19
+ score_changed: Array<{ id: string; before: number; after: number }>;
20
+ }
21
+
22
+ function packPath(projectRoot: string, runId: string, workItemId: string): string {
23
+ return path.join(projectRoot, '.oxe', 'runs', runId, `context-pack-${workItemId}.json`);
24
+ }
25
+
26
+ function metaIndexPath(projectRoot: string, runId: string): string {
27
+ return path.join(projectRoot, '.oxe', 'runs', runId, 'context-packs.index.json');
28
+ }
29
+
30
+ function estimateTokens(pack: ContextPack): number {
31
+ return Math.ceil(pack.artifacts.reduce((sum, a) => sum + a.content.length, 0) / 4);
32
+ }
33
+
34
+ export function savePack(
35
+ projectRoot: string,
36
+ runId: string,
37
+ pack: ContextPack
38
+ ): ContextPackMeta {
39
+ const p = packPath(projectRoot, runId, pack.work_item_id);
40
+ fs.mkdirSync(path.dirname(p), { recursive: true });
41
+ fs.writeFileSync(p, JSON.stringify(pack, null, 2), 'utf8');
42
+
43
+ const meta: ContextPackMeta = {
44
+ pack_id: `cp-${runId}-${pack.work_item_id}`,
45
+ work_item_id: pack.work_item_id,
46
+ run_id: runId,
47
+ built_at: pack.built_at,
48
+ artifact_count: pack.artifacts.length,
49
+ estimated_tokens: estimateTokens(pack),
50
+ stale: false,
51
+ stale_reason: null,
52
+ };
53
+
54
+ updateMetaIndex(projectRoot, runId, meta);
55
+ return meta;
56
+ }
57
+
58
+ export function loadPack(
59
+ projectRoot: string,
60
+ runId: string,
61
+ workItemId: string
62
+ ): ContextPack | null {
63
+ const p = packPath(projectRoot, runId, workItemId);
64
+ if (!fs.existsSync(p)) return null;
65
+ try {
66
+ return JSON.parse(fs.readFileSync(p, 'utf8')) as ContextPack;
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ export function markStale(
73
+ projectRoot: string,
74
+ runId: string,
75
+ workItemId: string,
76
+ reason: string
77
+ ): void {
78
+ const index = loadMetaIndex(projectRoot, runId);
79
+ const meta = index.find((m) => m.work_item_id === workItemId);
80
+ if (!meta) return;
81
+ meta.stale = true;
82
+ meta.stale_reason = reason;
83
+ saveMetaIndex(projectRoot, runId, index);
84
+ }
85
+
86
+ export function isStale(
87
+ projectRoot: string,
88
+ runId: string,
89
+ workItemId: string
90
+ ): boolean {
91
+ const index = loadMetaIndex(projectRoot, runId);
92
+ return index.find((m) => m.work_item_id === workItemId)?.stale ?? false;
93
+ }
94
+
95
+ export function diffPacks(before: ContextPack, after: ContextPack): ContextPackDiff {
96
+ const beforeIds = new Set(before.artifacts.map((a) => a.id));
97
+ const afterIds = new Set(after.artifacts.map((a) => a.id));
98
+ const beforeMap = new Map(before.artifacts.map((a) => [a.id, a]));
99
+ const afterMap = new Map(after.artifacts.map((a) => [a.id, a]));
100
+
101
+ const added = [...afterIds].filter((id) => !beforeIds.has(id));
102
+ const removed = [...beforeIds].filter((id) => !afterIds.has(id));
103
+
104
+ const score_changed: ContextPackDiff['score_changed'] = [];
105
+ for (const id of afterIds) {
106
+ if (!beforeIds.has(id)) continue;
107
+ const bScore = beforeMap.get(id)!.relevanceScore;
108
+ const aScore = afterMap.get(id)!.relevanceScore;
109
+ if (Math.abs(bScore - aScore) > 0.05) {
110
+ score_changed.push({ id, before: bScore, after: aScore });
111
+ }
112
+ }
113
+
114
+ return { added, removed, score_changed };
115
+ }
116
+
117
+ export function listPackMeta(projectRoot: string, runId: string): ContextPackMeta[] {
118
+ return loadMetaIndex(projectRoot, runId);
119
+ }
120
+
121
+ // ─── ContextPackRef — link pack to a specific execution attempt ───────────────
122
+
123
+ export interface ContextPackRef {
124
+ ref_id: string;
125
+ pack_id: string;
126
+ attempt_id: string;
127
+ work_item_id: string;
128
+ run_id: string;
129
+ artifacts_used: string[];
130
+ quality: ContextQualityScore;
131
+ linked_at: string;
132
+ }
133
+
134
+ function packRefPath(projectRoot: string, runId: string, attemptId: string): string {
135
+ return path.join(projectRoot, '.oxe', 'runs', runId, `context-ref-${attemptId}.json`);
136
+ }
137
+
138
+ export function linkPackToAttempt(
139
+ projectRoot: string,
140
+ runId: string,
141
+ attemptId: string,
142
+ pack: ContextPack,
143
+ quality: ContextQualityScore
144
+ ): ContextPackRef {
145
+ const ref: ContextPackRef = {
146
+ ref_id: `ref-${runId}-${attemptId}`,
147
+ pack_id: `cp-${runId}-${pack.work_item_id}`,
148
+ attempt_id: attemptId,
149
+ work_item_id: pack.work_item_id,
150
+ run_id: runId,
151
+ artifacts_used: pack.artifacts.map((a) => a.id),
152
+ quality,
153
+ linked_at: new Date().toISOString(),
154
+ };
155
+ const p = packRefPath(projectRoot, runId, attemptId);
156
+ fs.mkdirSync(path.dirname(p), { recursive: true });
157
+ fs.writeFileSync(p, JSON.stringify(ref, null, 2), 'utf8');
158
+ return ref;
159
+ }
160
+
161
+ export function getPackRefForAttempt(
162
+ projectRoot: string,
163
+ runId: string,
164
+ attemptId: string
165
+ ): ContextPackRef | null {
166
+ const p = packRefPath(projectRoot, runId, attemptId);
167
+ if (!fs.existsSync(p)) return null;
168
+ try {
169
+ return JSON.parse(fs.readFileSync(p, 'utf8')) as ContextPackRef;
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+
175
+ function loadMetaIndex(projectRoot: string, runId: string): ContextPackMeta[] {
176
+ const p = metaIndexPath(projectRoot, runId);
177
+ if (!fs.existsSync(p)) return [];
178
+ try {
179
+ return JSON.parse(fs.readFileSync(p, 'utf8')) as ContextPackMeta[];
180
+ } catch {
181
+ return [];
182
+ }
183
+ }
184
+
185
+ function saveMetaIndex(projectRoot: string, runId: string, index: ContextPackMeta[]): void {
186
+ const p = metaIndexPath(projectRoot, runId);
187
+ fs.mkdirSync(path.dirname(p), { recursive: true });
188
+ fs.writeFileSync(p, JSON.stringify(index, null, 2), 'utf8');
189
+ }
190
+
191
+ function updateMetaIndex(projectRoot: string, runId: string, meta: ContextPackMeta): void {
192
+ const index = loadMetaIndex(projectRoot, runId);
193
+ const idx = index.findIndex((m) => m.work_item_id === meta.work_item_id);
194
+ if (idx >= 0) index[idx] = meta;
195
+ else index.push(meta);
196
+ saveMetaIndex(projectRoot, runId, index);
197
+ }
@@ -0,0 +1,60 @@
1
+ import type { ContextArtifact } from './context-pack-builder';
2
+
3
+ export type ContextDecisionType = 'execute' | 'verify' | 'plan' | 'review' | 'debug' | 'migration';
4
+
5
+ export interface ContextProfile {
6
+ decision_type: ContextDecisionType;
7
+ artifact_kind_weights: Record<ContextArtifact['kind'], number>;
8
+ quality_threshold: number;
9
+ max_artifacts: number;
10
+ max_tokens: number;
11
+ }
12
+
13
+ export const DEFAULT_PROFILES: Record<ContextDecisionType, ContextProfile> = {
14
+ execute: {
15
+ decision_type: 'execute',
16
+ artifact_kind_weights: { evidence: 0.8, lesson: 0.3, file: 0.6, summary: 0.4 },
17
+ quality_threshold: 0.6,
18
+ max_artifacts: 20,
19
+ max_tokens: 8000,
20
+ },
21
+ verify: {
22
+ decision_type: 'verify',
23
+ artifact_kind_weights: { evidence: 0.9, lesson: 0.2, file: 0.5, summary: 0.5 },
24
+ quality_threshold: 0.7,
25
+ max_artifacts: 15,
26
+ max_tokens: 6000,
27
+ },
28
+ plan: {
29
+ decision_type: 'plan',
30
+ artifact_kind_weights: { evidence: 0.4, lesson: 0.9, file: 0.7, summary: 0.6 },
31
+ quality_threshold: 0.5,
32
+ max_artifacts: 25,
33
+ max_tokens: 10000,
34
+ },
35
+ review: {
36
+ decision_type: 'review',
37
+ artifact_kind_weights: { evidence: 0.6, lesson: 0.5, file: 0.8, summary: 0.5 },
38
+ quality_threshold: 0.55,
39
+ max_artifacts: 20,
40
+ max_tokens: 8000,
41
+ },
42
+ debug: {
43
+ decision_type: 'debug',
44
+ artifact_kind_weights: { evidence: 0.9, lesson: 0.6, file: 0.7, summary: 0.4 },
45
+ quality_threshold: 0.6,
46
+ max_artifacts: 20,
47
+ max_tokens: 8000,
48
+ },
49
+ migration: {
50
+ decision_type: 'migration',
51
+ artifact_kind_weights: { evidence: 0.7, lesson: 0.7, file: 0.9, summary: 0.5 },
52
+ quality_threshold: 0.65,
53
+ max_artifacts: 30,
54
+ max_tokens: 12000,
55
+ },
56
+ };
57
+
58
+ export function getProfile(decisionType: ContextDecisionType): ContextProfile {
59
+ return DEFAULT_PROFILES[decisionType];
60
+ }
@@ -1 +1,3 @@
1
1
  export * from './context-pack-builder';
2
+ export * from './context-pack-store';
3
+ export * from './context-profiles';