@aria_asi/cli 0.2.31 → 0.2.33

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 (79) hide show
  1. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  2. package/dist/aria-connector/src/connectors/claude-code.js +30 -3
  3. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  4. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +8 -1
  5. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -1
  6. package/dist/aria-connector/src/connectors/codebase-awareness.js +126 -71
  7. package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -1
  8. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  9. package/dist/aria-connector/src/connectors/codex.js +76 -9
  10. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  11. package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -1
  12. package/dist/aria-connector/src/connectors/must-read.js +4 -0
  13. package/dist/aria-connector/src/connectors/must-read.js.map +1 -1
  14. package/dist/aria-connector/src/connectors/opencode.js +25 -9
  15. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  16. package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
  17. package/dist/aria-connector/src/setup-wizard.js +91 -24
  18. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  19. package/dist/assets/hooks/aria-agent-handoff.mjs +23 -0
  20. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +69 -3
  21. package/dist/assets/hooks/aria-harness-via-sdk.mjs +10 -5
  22. package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +35 -0
  23. package/dist/assets/hooks/aria-pre-tool-gate.mjs +217 -17
  24. package/dist/assets/hooks/aria-preprompt-consult.mjs +28 -2
  25. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +30 -2
  26. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +31 -1
  27. package/dist/assets/hooks/aria-stop-gate.mjs +154 -37
  28. package/dist/assets/hooks/doctrine_trigger_map.json +55 -0
  29. package/dist/assets/hooks/lib/domain-output-quality.mjs +103 -0
  30. package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -0
  31. package/dist/assets/opencode-plugins/harness-gate/index.js +84 -7
  32. package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -0
  33. package/dist/assets/opencode-plugins/harness-outcome/index.js +39 -0
  34. package/dist/assets/opencode-plugins/harness-stop/index.js +101 -7
  35. package/dist/assets/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
  36. package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -0
  37. package/dist/runtime/codex-bridge.mjs +71 -8
  38. package/dist/runtime/discipline/CLAUDE.md +16 -0
  39. package/dist/runtime/discipline/doctrine_trigger_map.json +55 -0
  40. package/dist/runtime/doctrine_trigger_map.json +55 -0
  41. package/dist/runtime/harness-daemon.mjs +80 -5
  42. package/dist/runtime/manifest.json +1 -1
  43. package/dist/runtime/sdk/BUNDLED.json +1 -1
  44. package/dist/runtime/sdk/index.d.ts +14 -0
  45. package/dist/runtime/sdk/index.js +23 -1
  46. package/dist/runtime/sdk/index.js.map +1 -1
  47. package/dist/runtime/service.mjs +385 -11
  48. package/dist/sdk/BUNDLED.json +1 -1
  49. package/dist/sdk/index.d.ts +14 -0
  50. package/dist/sdk/index.js +23 -1
  51. package/dist/sdk/index.js.map +1 -1
  52. package/hooks/aria-agent-handoff.mjs +23 -0
  53. package/hooks/aria-cognition-substrate-binding.mjs +69 -3
  54. package/hooks/aria-harness-via-sdk.mjs +10 -5
  55. package/hooks/aria-pre-emit-dryrun.mjs +35 -0
  56. package/hooks/aria-pre-tool-gate.mjs +217 -17
  57. package/hooks/aria-preprompt-consult.mjs +28 -2
  58. package/hooks/aria-preturn-memory-gate.mjs +30 -2
  59. package/hooks/aria-repo-doctrine-gate.mjs +31 -1
  60. package/hooks/aria-stop-gate.mjs +154 -37
  61. package/hooks/doctrine_trigger_map.json +55 -0
  62. package/hooks/lib/domain-output-quality.mjs +103 -0
  63. package/hooks/lib/skill-autoload-gate.mjs +1 -0
  64. package/opencode-plugins/harness-gate/index.js +84 -7
  65. package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -0
  66. package/opencode-plugins/harness-outcome/index.js +39 -0
  67. package/opencode-plugins/harness-stop/index.js +101 -7
  68. package/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
  69. package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -0
  70. package/package.json +1 -1
  71. package/runtime-src/codex-bridge.mjs +71 -8
  72. package/runtime-src/harness-daemon.mjs +80 -5
  73. package/runtime-src/service.mjs +385 -11
  74. package/src/connectors/claude-code.ts +31 -3
  75. package/src/connectors/codebase-awareness.ts +141 -77
  76. package/src/connectors/codex.ts +76 -9
  77. package/src/connectors/must-read.ts +4 -0
  78. package/src/connectors/opencode.ts +25 -9
  79. package/src/setup-wizard.ts +105 -25
@@ -3,6 +3,7 @@
3
3
  * Routes through the canonical SDK for outcome/garden/ledger writes.
4
4
  */
5
5
  import { existsSync, readFileSync } from 'node:fs';
6
+ import { createHash } from 'node:crypto';
6
7
  import { homedir } from 'node:os';
7
8
  import { join } from 'node:path';
8
9
 
@@ -14,6 +15,7 @@ const SDK_CANDIDATES = [
14
15
  ];
15
16
  const OWNER_TOKEN_PATH = join(HOME, '.aria', 'owner-token');
16
17
  const LICENSE_PATH = join(HOME, '.aria', 'license.json');
18
+ const RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\/+$/, '');
17
19
 
18
20
  let _client = null;
19
21
  let _clientError = null;
@@ -46,6 +48,32 @@ function resolveSdkPath() {
46
48
  return SDK_CANDIDATES.find((candidate) => existsSync(candidate)) || '';
47
49
  }
48
50
 
51
+ function makeEvidenceRef(kind, value, metadata = {}) {
52
+ const raw = typeof value === 'string' ? value : JSON.stringify(value ?? null);
53
+ const sha256 = createHash('sha256').update(raw).digest('hex');
54
+ return {
55
+ evidenceId: `ev_${sha256.slice(0, 16)}`,
56
+ kind,
57
+ at: new Date().toISOString(),
58
+ sha256,
59
+ preview: raw.slice(0, 500),
60
+ metadata,
61
+ };
62
+ }
63
+
64
+ async function releaseHiveLease(sessionId, files) {
65
+ const token = resolveToken();
66
+ if (!token || !Array.isArray(files) || files.length === 0) return;
67
+ await fetch(`${RUNTIME_URL}/hive/release`, {
68
+ method: 'POST',
69
+ headers: {
70
+ 'Content-Type': 'application/json',
71
+ Authorization: `Bearer ${token}`,
72
+ },
73
+ body: JSON.stringify({ sessionId, files }),
74
+ }).catch(() => {});
75
+ }
76
+
49
77
  async function getClient() {
50
78
  if (_client) return _client;
51
79
  if (_clientError) return null;
@@ -78,6 +106,15 @@ export default async function HarnessOutcomePlugin(ctx) {
78
106
  : String(input.args?.file_path ?? input.args?.notebook_path ?? '').slice(0, 200) || 'unknown';
79
107
  const success = !output?.error;
80
108
  const sessionId = process.env.ARIA_SESSION_ID || 'opencode';
109
+ const filePath = toolName !== 'Bash' ? String(input.args?.file_path ?? input.args?.notebook_path ?? '') : '';
110
+ await releaseHiveLease(sessionId, filePath ? [filePath] : []);
111
+ const outcomeRef = makeEvidenceRef('opencode_tool_outcome', {
112
+ toolName,
113
+ actionKind,
114
+ actionTarget,
115
+ success,
116
+ output: output ?? null,
117
+ }, { sessionId });
81
118
 
82
119
  // Try SDK first
83
120
  const client = await getClient();
@@ -86,6 +123,7 @@ export default async function HarnessOutcomePlugin(ctx) {
86
123
  await client.recordDiscovery({
87
124
  text: `${actionKind}: ${actionTarget} — ${success ? 'ok' : 'failed'}`,
88
125
  kind: success ? 'observation' : 'defect',
126
+ evidence: JSON.stringify(outcomeRef),
89
127
  source: 'opencode-outcome-plugin',
90
128
  resolution_status: success ? 'resolved' : 'open',
91
129
  sessionId,
@@ -122,6 +160,7 @@ export default async function HarnessOutcomePlugin(ctx) {
122
160
  actionKind,
123
161
  actionTarget,
124
162
  actionShape: { tool: toolName, success },
163
+ evidenceRef: outcomeRef,
125
164
  }),
126
165
  }).catch(() => {});
127
166
  },
@@ -3,8 +3,11 @@
3
3
  * Routes text through Mizan validateOutput() for substrate-backed QC.
4
4
  */
5
5
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
6
+ import { createHash } from 'node:crypto';
6
7
  import { homedir } from 'node:os';
7
8
  import { join } from 'node:path';
9
+ import { analyzeDomainOutputQuality, extractCodeBlocks } from './lib/domain-output-quality.js';
10
+ import { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.js';
8
11
 
9
12
  const HOME = homedir();
10
13
  const SDK_CANDIDATES = [
@@ -29,6 +32,22 @@ const NON_TRIVIAL_MIN_CHARS = 300;
29
32
  const DECISION_SIGNAL_RX = /(?:should|recommend|propose|suggest|let'?s|go with|i'd|i would|here'?s the plan|i'll|next step|action item|ship it|yes do|let me)/i;
30
33
  const TRIVIAL_ACK_RX = /^(?:got it|on it|ok|sure|yes|no|done|ack)\b/i;
31
34
  const PLACEHOLDER_RX = /^\s*<[^<>]+>\s*$/;
35
+ const BLOCK_PREFIX_RX = /^=== ARIA (?:MIZAN POST|OUTPUT GATE|LOCAL OUTPUT) BLOCK ===/;
36
+ const APPLIED_COGNITION_RX = /<applied_cognition>[\s\S]*?decision_delta\s*:[\s\S]*?dominant_domain\s*:[\s\S]*?binds_to\s*:[\s\S]*?expected_predicate\s*:[\s\S]*?artifact_change\s*:[\s\S]*?<\/applied_cognition>/i;
37
+
38
+ function formatBlockReason(prefix, details) {
39
+ return [
40
+ prefix,
41
+ '',
42
+ String(details || 'Aria output gate blocked this message.'),
43
+ '',
44
+ 'Required repair: bind cognition to the actual action/output using <applied_cognition> with decision_delta, dominant_domain, binds_to, expected_predicate, and artifact_change.',
45
+ ].join('\n');
46
+ }
47
+
48
+ function isGateBlock(error) {
49
+ return BLOCK_PREFIX_RX.test(String(error?.message || error || ''));
50
+ }
32
51
 
33
52
  let _client = null;
34
53
  let _clientError = null;
@@ -55,6 +74,19 @@ function persistReceiptState(sessionId, payload) {
55
74
  } catch {}
56
75
  }
57
76
 
77
+ function makeEvidenceRef(kind, value, metadata = {}) {
78
+ const raw = typeof value === 'string' ? value : JSON.stringify(value ?? null);
79
+ const sha256 = createHash('sha256').update(raw).digest('hex');
80
+ return {
81
+ evidenceId: `ev_${sha256.slice(0, 16)}`,
82
+ kind,
83
+ at: new Date().toISOString(),
84
+ sha256,
85
+ preview: raw.slice(0, 500),
86
+ metadata,
87
+ };
88
+ }
89
+
58
90
  function resolveToken() {
59
91
  if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
60
92
  if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
@@ -121,7 +153,7 @@ async function runtimeValidateOutput(text, sessionId) {
121
153
  return payload.validation || payload.result || payload;
122
154
  }
123
155
 
124
- async function runtimeMizanPost(text, sessionId, context = {}) {
156
+ async function runtimeMizanPost(text, sessionId, context = {}, evidence = {}) {
125
157
  const existing = loadReceiptState(sessionId);
126
158
  const token = resolveToken();
127
159
  if (!token) throw new Error('no token');
@@ -134,10 +166,22 @@ async function runtimeMizanPost(text, sessionId, context = {}) {
134
166
  body: JSON.stringify({
135
167
  sessionId,
136
168
  text,
169
+ evidence,
137
170
  context: {
138
171
  sessionId,
172
+ actor: 'opencode',
173
+ system: 'opencode',
174
+ platform: 'opencode',
175
+ surface: 'opencode-harness-stop',
139
176
  ...context,
140
177
  },
178
+ packetRequest: {
179
+ stage: 'preflight',
180
+ actor: 'opencode',
181
+ system: 'opencode',
182
+ platform: 'opencode',
183
+ message: String(context.message || text || '').slice(0, 1000),
184
+ },
141
185
  parentReceiptId: existing?.receipt?.receiptId || _lastMizanReceipt?.receiptId || null,
142
186
  }),
143
187
  });
@@ -207,10 +251,30 @@ export default async function HarnessStopPlugin(ctx) {
207
251
 
208
252
  const cog = detectCognitionLenses(text);
209
253
  const sessionId = process.env.ARIA_SESSION_ID || 'opencode';
254
+ const skillGate = evaluateSkillGate({
255
+ sessionId,
256
+ surface: 'opencode-harness-stop',
257
+ text: JSON.stringify(event || {}) + '\n' + text,
258
+ isOutputCloseout: true,
259
+ autoLoadAvailable: false,
260
+ });
261
+ if (!skillGate.ok) {
262
+ throw new Error(
263
+ formatBlockReason(
264
+ '=== ARIA SKILL AUTOLOAD GATE BLOCK ===',
265
+ `${formatSkillGateBlock(skillGate)}\nRequired next step: load ${skillGate.missingSkills.join(', ')} before re-emitting this output.`,
266
+ )
267
+ );
268
+ }
269
+ const outputRef = makeEvidenceRef('opencode_assistant_output', text.slice(0, 8000), { sessionId });
210
270
  try {
211
271
  const mizan = await runtimeMizanPost(text.slice(0, 8000), sessionId, {
212
272
  message: text.slice(0, 1000),
213
273
  plannedApproach: 'OpenCode stop gate output review',
274
+ outputRef,
275
+ }, {
276
+ output_ref: outputRef,
277
+ cognition_lens_count: cog.count,
214
278
  });
215
279
  _lastMizanReceipt = mizan.receipt || _lastMizanReceipt;
216
280
  if (_lastMizanReceipt) {
@@ -223,14 +287,17 @@ export default async function HarnessStopPlugin(ctx) {
223
287
  postResult: mizan.result || null,
224
288
  postContract: mizan.contract || null,
225
289
  postSummary: mizan.summary || null,
290
+ outputRef,
226
291
  });
227
292
  }
228
293
  if (mizan.receipt?.blocked || mizan.result?.fitrahVetoed || mizan.result?.reAuthorSignal) {
229
- process.stderr.write(
230
- `[harness-stop] MIZAN POST BLOCK — ${(mizan.result?.notes || ['post-phase blocked']).slice(0, 4).join(' | ')}\n`
294
+ const details = (mizan.result?.notes || ['post-phase blocked']).slice(0, 4).join(' | ');
295
+ throw new Error(
296
+ formatBlockReason('=== ARIA MIZAN POST BLOCK ===', details)
231
297
  );
232
298
  }
233
299
  } catch (e) {
300
+ if (isGateBlock(e)) throw e;
234
301
  process.stderr.write(`[harness-stop] mizan/post unavailable: ${e.message}\n`);
235
302
  }
236
303
 
@@ -246,8 +313,11 @@ export default async function HarnessStopPlugin(ctx) {
246
313
  sessionId,
247
314
  ));
248
315
  if (result.severity === 'block') {
249
- process.stderr.write(
250
- `[harness-stop] SDK BLOCK — ${result.violations.length} violations: ${result.violations.join('; ').slice(0, 300)}\n`
316
+ throw new Error(
317
+ formatBlockReason(
318
+ '=== ARIA OUTPUT GATE BLOCK ===',
319
+ `${result.violations.length} violations: ${result.violations.join('; ').slice(0, 500)}`,
320
+ )
251
321
  );
252
322
  } else if (result.severity === 'warn') {
253
323
  process.stderr.write(
@@ -260,6 +330,7 @@ export default async function HarnessStopPlugin(ctx) {
260
330
  }
261
331
  return;
262
332
  } catch (e) {
333
+ if (isGateBlock(e)) throw e;
263
334
  process.stderr.write(`[harness-stop] SDK validateOutput failed: ${e.message} — falling through to local gate\n`);
264
335
  }
265
336
  }
@@ -293,8 +364,30 @@ export default async function HarnessStopPlugin(ctx) {
293
364
  } catch {}
294
365
 
295
366
  if (cog.count < REQUIRED_LENSES || driftHits.length >= 2) {
296
- process.stderr.write(
297
- `[harness-stop] LOCAL GATE — cognition=${cog.count}/${REQUIRED_LENSES} drift=${driftHits.length}\n`
367
+ throw new Error(
368
+ formatBlockReason(
369
+ '=== ARIA LOCAL OUTPUT BLOCK ===',
370
+ `cognition=${cog.count}/${REQUIRED_LENSES}; drift=${driftHits.length}`,
371
+ )
372
+ );
373
+ }
374
+
375
+ if (DECISION_SIGNAL_RX.test(text) && !APPLIED_COGNITION_RX.test(text)) {
376
+ throw new Error(
377
+ formatBlockReason(
378
+ '=== ARIA LOCAL OUTPUT BLOCK ===',
379
+ 'decision-bearing output lacks required applied_cognition binding fields',
380
+ )
381
+ );
382
+ }
383
+
384
+ const domainQuality = analyzeDomainOutputQuality(text, { codeBlocks: extractCodeBlocks(text) });
385
+ if (domainQuality.blockers.length > 0) {
386
+ throw new Error(
387
+ formatBlockReason(
388
+ '=== ARIA LOCAL OUTPUT BLOCK ===',
389
+ `domain output QA (${domainQuality.domains.join(', ') || 'general'}): ${domainQuality.blockers.join('; ')}`,
390
+ )
298
391
  );
299
392
  }
300
393
 
@@ -318,6 +411,7 @@ export default async function HarnessStopPlugin(ctx) {
318
411
  metadata: {
319
412
  pre_receipt_id: existing?.receipt?.receiptId || null,
320
413
  post_receipt_id: _lastMizanReceipt?.receiptId || null,
414
+ output_ref: outputRef,
321
415
  },
322
416
  source: 'opencode-stop-gate-runtime',
323
417
  model_used: process.env.OPENCODE_MODEL || 'opencode',
@@ -0,0 +1,103 @@
1
+ // Domain-aware output QA for Aria stop gates.
2
+ // Deterministic local checks complement remote Mizan; they do not replace it.
3
+
4
+ const DOMAIN_RULES = [
5
+ {
6
+ domain: 'code',
7
+ signal: /```|\b(?:function|class|interface|type|import|export|npm test|typecheck|eslint|jest|vitest|tsx?|jsx?|\.ts|\.tsx|\.js|\.py)\b/i,
8
+ },
9
+ {
10
+ domain: 'ui',
11
+ signal: /\b(?:ui|ux|frontend|react|component|page|screen|modal|form|button|layout|tailwind|css|html|mobile|responsive|accessib|aria-label|keyboard|focus state|dark mode)\b/i,
12
+ },
13
+ {
14
+ domain: 'beauty',
15
+ signal: /\b(?:beauty|beautiful|polish|visual|aesthetic|elegant|layout|typography|spacing|hierarchy|composition|brand|design language|modern|clean)\b/i,
16
+ },
17
+ {
18
+ domain: 'security',
19
+ signal: /\b(?:security|auth|token|secret|credential|password|jwt|oauth|csrf|xss|sql injection|permission|role|cors|sanitize|encrypt|webhook signature)\b/i,
20
+ },
21
+ {
22
+ domain: 'ops',
23
+ signal: /\b(?:deploy|rollout|rollback|kubernetes|kubectl|docker|image|pod|health check|slo|alert|log|metric|trace|env var|migration|release)\b/i,
24
+ },
25
+ {
26
+ domain: 'product',
27
+ signal: /\b(?:user flow|customer|workflow|conversion|pricing|onboarding|checkout|activation|retention|business|persona|job-to-be-done|acceptance criteria)\b/i,
28
+ },
29
+ {
30
+ domain: 'writing',
31
+ signal: /\b(?:summary|explain|docs|readme|copy|email|post|article|message|final answer|status report|release notes)\b/i,
32
+ },
33
+ ];
34
+
35
+ function hasAny(text, patterns) {
36
+ return patterns.some((pattern) => pattern.test(text));
37
+ }
38
+
39
+ function lineHits(text, rx, label) {
40
+ const hits = [];
41
+ const lines = String(text || '').split('\n');
42
+ for (let i = 0; i < lines.length; i++) {
43
+ if (rx.test(lines[i])) hits.push(`${label} at line ${i + 1}`);
44
+ }
45
+ return hits;
46
+ }
47
+
48
+ export function extractCodeBlocks(text) {
49
+ return [...String(text || '').matchAll(/```[a-z0-9_-]*\n([\s\S]*?)```/gi)].map((m) => m[1] || '');
50
+ }
51
+
52
+ export function analyzeDomainOutputQuality(text, options = {}) {
53
+ const source = String(text || '');
54
+ const lower = source.toLowerCase();
55
+ const codeBlocks = options.codeBlocks || extractCodeBlocks(source);
56
+ const domains = DOMAIN_RULES.filter((rule) => rule.signal.test(source)).map((rule) => rule.domain);
57
+ const blockers = [];
58
+ const warnings = [];
59
+
60
+ if (domains.includes('code')) {
61
+ for (const hit of lineHits(source, /\b(?:TODO|FIXME|XXX|implementation pending|not implemented|coming soon|placeholder)\b/i, 'code placeholder semantics')) blockers.push(hit);
62
+ for (const block of codeBlocks) {
63
+ if (/@ts-expect-error|@ts-ignore/.test(block)) blockers.push('code: type suppression instead of fixing the type contract');
64
+ if (/catch\s*\([^)]*\)\s*\{\s*(?:return\s+(?:''|""|null|undefined|\[\]|\{\})|\}\s*$|\/\/[^\n]*$)/m.test(block)) blockers.push('code: silent or empty catch block hides runtime failure');
65
+ if (/console\.log\(/.test(block) && !/\/\/\s*(?:debug|log)/i.test(block)) warnings.push('code: console.log appears in shipped code without debug intent');
66
+ }
67
+ }
68
+
69
+ if (domains.includes('ui')) {
70
+ if (!hasAny(source, [/\b(?:mobile|responsive|breakpoint|small screen|desktop)\b/i])) blockers.push('ui: UI/design output must address desktop and mobile responsiveness');
71
+ if (!hasAny(source, [/\b(?:accessib|aria-|keyboard|focus|screen reader|semantic html|label)\b/i])) blockers.push('ui: UI/design output must address accessibility, focus, labels, or semantic structure');
72
+ if (/\b(?:button|input|form|modal|menu|dialog)\b/i.test(source) && !/\b(?:focus|keyboard|aria-|label|escape|tab order)\b/i.test(source)) blockers.push('ui: interactive elements need keyboard/focus/accessibility behavior');
73
+ }
74
+
75
+ if (domains.includes('beauty')) {
76
+ if (/\b(?:clean|modern|beautiful|polished|nice|sleek)\b/i.test(source) && !hasAny(source, [/\b(?:spacing|typography|contrast|hierarchy|rhythm|composition|palette|motion|density|visual language)\b/i])) blockers.push('beauty: aesthetic claim lacks concrete visual language such as spacing, typography, contrast, hierarchy, palette, or composition');
77
+ }
78
+
79
+ if (domains.includes('security')) {
80
+ if (/\b(?:token|secret|credential|password|api key|jwt)\b/i.test(source) && !/\b(?:redact|mask|env|secret store|do not log|rotate|least privilege)\b/i.test(source)) blockers.push('security: secrets/tokens require redaction, env/secret-store handling, no logging, or rotation guidance');
81
+ if (/\b(?:auth|permission|role|admin|tenant)\b/i.test(source) && !/\b(?:authorize|authorization|least privilege|tenant isolation|deny by default|role check)\b/i.test(source)) warnings.push('security: auth/permission output should state authorization and isolation checks');
82
+ }
83
+
84
+ if (domains.includes('ops')) {
85
+ if (/\b(?:deploy|rollout|release|migration|kubectl|docker push)\b/i.test(source) && !/\b(?:rollback|health|smoke|verify|readiness|observability|monitor)\b/i.test(source)) blockers.push('ops: deploy/release output must include rollback plus health/smoke/readiness verification');
86
+ if (/\b(?:env var|config|secret)\b/i.test(source) && !/\b(?:scope|owner|runtime|restart|reload|secret)\b/i.test(source)) warnings.push('ops: runtime config changes should name scope and reload/restart expectations');
87
+ }
88
+
89
+ if (domains.includes('product')) {
90
+ if (!/\b(?:user|customer|operator|admin|persona|workflow|acceptance criteria|success metric|job-to-be-done)\b/i.test(source)) warnings.push('product: product output should bind to a user/workflow and success criterion');
91
+ }
92
+
93
+ if (domains.includes('writing')) {
94
+ if (/\b(?:done|fixed|verified|published|deployed|passed|complete)\b/i.test(lower) && !/\b(?:verified|observed|passed|evidence|output|registry|status|unverified|not verified)\b/i.test(lower)) blockers.push('writing: completion claim needs observed evidence or explicit unverified boundary');
95
+ if (/\b(?:should work|probably|presumably|i assume)\b/i.test(source)) blockers.push('writing: uncertainty must be stated as an evidence boundary, not an assumption');
96
+ }
97
+
98
+ return {
99
+ domains: [...new Set(domains)],
100
+ blockers: [...new Set(blockers)],
101
+ warnings: [...new Set(warnings)],
102
+ };
103
+ }
@@ -0,0 +1 @@
1
+ export * from '../../../../../ops/claude-hooks/lib/skill-autoload-gate.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aria_asi/cli",
3
- "version": "0.2.31",
3
+ "version": "0.2.33",
4
4
  "description": "Aria Smart CLI \u2014 the world's first harness-powered terminal companion",
5
5
  "bin": {
6
6
  "aria": "./bin/aria.js"
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
4
+ import { createHash, randomUUID } from 'node:crypto';
4
5
  import { homedir } from 'node:os';
5
6
  import { delimiter, dirname, resolve } from 'node:path';
6
7
  import { spawn, spawnSync } from 'node:child_process';
7
8
  import { createRequire } from 'node:module';
9
+ import { evaluateSkillGate, formatSkillGateBlock } from '../../../ops/claude-hooks/lib/skill-autoload-gate.mjs';
8
10
 
9
11
  const require = createRequire(import.meta.url);
10
12
  const { WebSocketServer, WebSocket } = require('ws');
@@ -75,6 +77,24 @@ function sleep(ms) {
75
77
  return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
76
78
  }
77
79
 
80
+ function stableEvidenceString(value) {
81
+ if (typeof value === 'string') return value;
82
+ try { return JSON.stringify(value); } catch { return String(value); }
83
+ }
84
+
85
+ function makeEvidenceRef(kind, value, metadata = {}) {
86
+ const raw = stableEvidenceString(value);
87
+ const sha256 = createHash('sha256').update(raw).digest('hex');
88
+ return {
89
+ evidenceId: `ev_${sha256.slice(0, 16)}`,
90
+ kind,
91
+ at: new Date().toISOString(),
92
+ sha256,
93
+ preview: raw.slice(0, 500),
94
+ metadata,
95
+ };
96
+ }
97
+
78
98
  function readRuntimeToken() {
79
99
  const envToken = process.env.ARIA_HARNESS_TOKEN || process.env.ARIA_API_KEY || process.env.OPENAI_API_KEY || process.env.ARIA_MASTER_TOKEN;
80
100
  if (envToken) return envToken;
@@ -123,9 +143,10 @@ function ensureTurnState(threadId, turnId) {
123
143
  userText: '',
124
144
  preReceiptId: null,
125
145
  agentText: '',
126
- bufferedAgentNotifications: [],
127
- firstAgentItemId: null,
128
- };
146
+ bufferedAgentNotifications: [],
147
+ firstAgentItemId: null,
148
+ traceId: `trace_${randomUUID()}`,
149
+ };
129
150
  turnState.set(key, state);
130
151
  }
131
152
  return state;
@@ -186,6 +207,16 @@ async function validateTurnText(threadId, turnId) {
186
207
  if (!text) {
187
208
  return { ok: false, reason: 'No assistant text exists for this turn yet. Codex must emit readable cognition before action.' };
188
209
  }
210
+ const skillGate = evaluateSkillGate({
211
+ sessionId: `codex:${threadId}:${turnId}`,
212
+ surface: 'codex-bridge-output',
213
+ text: [state.userText, text].join('\n'),
214
+ isOutputCloseout: true,
215
+ autoLoadAvailable: false,
216
+ });
217
+ if (!skillGate.ok) {
218
+ return { ok: false, reason: formatSkillGateBlock(skillGate), result: { skillGate } };
219
+ }
189
220
  const result = await postRuntime('/validate-output', {
190
221
  text,
191
222
  sessionId: `codex:${threadId}:${turnId}`,
@@ -198,6 +229,7 @@ async function validateTurnText(threadId, turnId) {
198
229
  stage: 'codex-turn',
199
230
  actor: 'codex-bridge',
200
231
  system: 'codex-bridge',
232
+ traceId: state.traceId,
201
233
  },
202
234
  });
203
235
  const pass = result?.pass === true && result?.validation?.passed !== false;
@@ -210,11 +242,29 @@ async function validateTurnText(threadId, turnId) {
210
242
  };
211
243
  }
212
244
 
213
- async function checkActionAgainstRuntime(action, target, threadId, turnId) {
245
+ async function checkActionAgainstRuntime(action, target, threadId, turnId, metadata = {}) {
246
+ const state = ensureTurnState(threadId, turnId);
247
+ const skillGate = evaluateSkillGate({
248
+ sessionId: `codex:${threadId}:${turnId}`,
249
+ surface: 'codex-bridge-action',
250
+ text: target,
251
+ action: target,
252
+ toolName: action,
253
+ isDeploy: action === 'deploy',
254
+ isMutation: action === 'write',
255
+ autoLoadAvailable: false,
256
+ });
257
+ if (!skillGate.ok) {
258
+ return { ok: false, reason: formatSkillGateBlock(skillGate), result: { skillGate } };
259
+ }
214
260
  const result = await postRuntime('/check-action', {
215
261
  action,
216
262
  target,
217
263
  sessionId: `codex:${threadId}:${turnId}`,
264
+ actor: 'codex',
265
+ roleProfile: process.env.CODEX_ARIA_ROLE_PROFILE || process.env.ARIA_ROLE_PROFILE || null,
266
+ verifyText: state.agentText || '',
267
+ ...metadata,
218
268
  });
219
269
  if (result?.allowed === false) {
220
270
  return {
@@ -244,6 +294,8 @@ async function recordMizanPre(threadId, turnId) {
244
294
  surface: 'codex-bridge',
245
295
  platform: 'codex',
246
296
  userText: state.userText,
297
+ traceId: state.traceId,
298
+ evidenceRefs: [makeEvidenceRef('user_input', state.userText, { threadId, turnId, traceId: state.traceId })],
247
299
  },
248
300
  });
249
301
  state.preReceiptId = result?.receipt?.receiptId || null;
@@ -262,6 +314,8 @@ async function recordMizanPost(threadId, turnId, pass, summary) {
262
314
  evidence: {
263
315
  validated_output: pass,
264
316
  bridge: 'codex',
317
+ trace_id: state.traceId,
318
+ output_ref: makeEvidenceRef('assistant_output', state.agentText || summary, { threadId, turnId, traceId: state.traceId }),
265
319
  },
266
320
  context: {
267
321
  sessionId: `codex:${threadId}:${turnId}`,
@@ -269,6 +323,7 @@ async function recordMizanPost(threadId, turnId, pass, summary) {
269
323
  platform: 'codex',
270
324
  userText: state.userText,
271
325
  summary,
326
+ traceId: state.traceId,
272
327
  },
273
328
  });
274
329
  } catch (error) {
@@ -285,6 +340,8 @@ async function recordMizanPost(threadId, turnId, pass, summary) {
285
340
  details: {
286
341
  threadId,
287
342
  turnId,
343
+ traceId: state.traceId,
344
+ outputRef: makeEvidenceRef('assistant_output', state.agentText || summary, { threadId, turnId, traceId: state.traceId }),
288
345
  },
289
346
  });
290
347
  } catch (error) {
@@ -336,7 +393,9 @@ async function handleApprovalRequest(upstream, downstream, message) {
336
393
  cwd: params.cwd || null,
337
394
  commandActions: params.commandActions || params.parsedCmd || null,
338
395
  }).slice(0, 1500);
339
- const actionCheck = await checkActionAgainstRuntime(action, target, threadId, turnId);
396
+ const actionCheck = await checkActionAgainstRuntime(action, target, threadId, turnId, {
397
+ requireVerify: action === 'delete' || action === 'deploy',
398
+ });
340
399
  if (!actionCheck.ok) {
341
400
  const reason = `Aria runtime denied ${action}: ${actionCheck.reason}`;
342
401
  upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
@@ -360,8 +419,9 @@ async function handleApprovalRequest(upstream, downstream, message) {
360
419
  }
361
420
 
362
421
  if (method === 'item/fileChange/requestApproval' || method === 'applyPatchApproval') {
363
- const target = params.grantRoot || params.reason || params.itemId || 'file-change';
364
- const actionCheck = await checkActionAgainstRuntime('write', String(target), threadId, turnId);
422
+ const target = params.grantRoot || params.path || params.filePath || params.reason || params.itemId || 'file-change';
423
+ const files = [params.path, params.filePath, params.grantRoot].filter((value) => typeof value === 'string' && value.trim());
424
+ const actionCheck = await checkActionAgainstRuntime('write', String(target), threadId, turnId, { files });
365
425
  if (!actionCheck.ok) {
366
426
  const reason = `Aria runtime denied file change: ${actionCheck.reason}`;
367
427
  upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
@@ -390,7 +450,10 @@ async function handleApprovalRequest(upstream, downstream, message) {
390
450
  permissions: params.permissions || null,
391
451
  reason: params.reason || null,
392
452
  }).slice(0, 1500);
393
- const actionCheck = await checkActionAgainstRuntime('write', target, threadId, turnId);
453
+ const files = Array.isArray(params.permissions?.fileSystem?.entries)
454
+ ? params.permissions.fileSystem.entries.filter((value) => typeof value === 'string' && value.trim())
455
+ : [];
456
+ const actionCheck = await checkActionAgainstRuntime('write', target, threadId, turnId, { files });
394
457
  if (!actionCheck.ok) {
395
458
  const reason = `Aria runtime denied permissions request: ${actionCheck.reason}`;
396
459
  upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));