@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.
- package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.js +30 -3
- package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
- package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +8 -1
- package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/codebase-awareness.js +126 -71
- package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -1
- package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/codex.js +76 -9
- package/dist/aria-connector/src/connectors/codex.js.map +1 -1
- package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/must-read.js +4 -0
- package/dist/aria-connector/src/connectors/must-read.js.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +25 -9
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
- package/dist/aria-connector/src/setup-wizard.js +91 -24
- package/dist/aria-connector/src/setup-wizard.js.map +1 -1
- package/dist/assets/hooks/aria-agent-handoff.mjs +23 -0
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +69 -3
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +10 -5
- package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +35 -0
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +217 -17
- package/dist/assets/hooks/aria-preprompt-consult.mjs +28 -2
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +30 -2
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +31 -1
- package/dist/assets/hooks/aria-stop-gate.mjs +154 -37
- package/dist/assets/hooks/doctrine_trigger_map.json +55 -0
- package/dist/assets/hooks/lib/domain-output-quality.mjs +103 -0
- package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -0
- package/dist/assets/opencode-plugins/harness-gate/index.js +84 -7
- package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -0
- package/dist/assets/opencode-plugins/harness-outcome/index.js +39 -0
- package/dist/assets/opencode-plugins/harness-stop/index.js +101 -7
- package/dist/assets/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
- package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -0
- package/dist/runtime/codex-bridge.mjs +71 -8
- package/dist/runtime/discipline/CLAUDE.md +16 -0
- package/dist/runtime/discipline/doctrine_trigger_map.json +55 -0
- package/dist/runtime/doctrine_trigger_map.json +55 -0
- package/dist/runtime/harness-daemon.mjs +80 -5
- package/dist/runtime/manifest.json +1 -1
- package/dist/runtime/sdk/BUNDLED.json +1 -1
- package/dist/runtime/sdk/index.d.ts +14 -0
- package/dist/runtime/sdk/index.js +23 -1
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/service.mjs +385 -11
- package/dist/sdk/BUNDLED.json +1 -1
- package/dist/sdk/index.d.ts +14 -0
- package/dist/sdk/index.js +23 -1
- package/dist/sdk/index.js.map +1 -1
- package/hooks/aria-agent-handoff.mjs +23 -0
- package/hooks/aria-cognition-substrate-binding.mjs +69 -3
- package/hooks/aria-harness-via-sdk.mjs +10 -5
- package/hooks/aria-pre-emit-dryrun.mjs +35 -0
- package/hooks/aria-pre-tool-gate.mjs +217 -17
- package/hooks/aria-preprompt-consult.mjs +28 -2
- package/hooks/aria-preturn-memory-gate.mjs +30 -2
- package/hooks/aria-repo-doctrine-gate.mjs +31 -1
- package/hooks/aria-stop-gate.mjs +154 -37
- package/hooks/doctrine_trigger_map.json +55 -0
- package/hooks/lib/domain-output-quality.mjs +103 -0
- package/hooks/lib/skill-autoload-gate.mjs +1 -0
- package/opencode-plugins/harness-gate/index.js +84 -7
- package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -0
- package/opencode-plugins/harness-outcome/index.js +39 -0
- package/opencode-plugins/harness-stop/index.js +101 -7
- package/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
- package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -0
- package/package.json +1 -1
- package/runtime-src/codex-bridge.mjs +71 -8
- package/runtime-src/harness-daemon.mjs +80 -5
- package/runtime-src/service.mjs +385 -11
- package/src/connectors/claude-code.ts +31 -3
- package/src/connectors/codebase-awareness.ts +141 -77
- package/src/connectors/codex.ts +76 -9
- package/src/connectors/must-read.ts +4 -0
- package/src/connectors/opencode.ts +25 -9
- package/src/setup-wizard.ts +105 -25
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* Routes through HTTPHarnessClient SDK for substrate-backed validation.
|
|
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 { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.js';
|
|
8
10
|
|
|
9
11
|
const HOME = homedir();
|
|
10
12
|
const SDK_CANDIDATES = [
|
|
@@ -42,7 +44,14 @@ const DEPLOY_PATTERNS = [
|
|
|
42
44
|
{ rx: /\bdocker\s+build\b.*--push\b/, name: 'docker-build-push' },
|
|
43
45
|
];
|
|
44
46
|
|
|
45
|
-
const
|
|
47
|
+
const INLINE_LENS_NAMES = [
|
|
48
|
+
'nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah',
|
|
49
|
+
'truth', 'harm', 'trust', 'power', 'reflection', 'context', 'impact', 'beauty',
|
|
50
|
+
];
|
|
51
|
+
const INLINE_LENS_RX = new RegExp(
|
|
52
|
+
`^\\s*#\\s*(?:${INLINE_LENS_NAMES.join('|')})\\s*:\\s*.{20,}`,
|
|
53
|
+
'gim',
|
|
54
|
+
);
|
|
46
55
|
const VERIFY_BLOCK_RX = /<verify>[\s\S]*?target\s*:[\s\S]*?verified\s*:[\s\S]*?axiom\s*:[\s\S]*?<\/verify>/i;
|
|
47
56
|
const TRIVIAL_BASH_RX = /^\s*(?:ls|cat|head|tail|grep|find|echo|wc|stat|which|pwd|date|file|du|df|ss|ps)\s/;
|
|
48
57
|
const SHORT_BASH_LIMIT = 30;
|
|
@@ -63,6 +72,28 @@ function persistReceipt(sessionId, payload) {
|
|
|
63
72
|
} catch {}
|
|
64
73
|
}
|
|
65
74
|
|
|
75
|
+
function makeEvidenceRef(kind, value, metadata = {}) {
|
|
76
|
+
const raw = typeof value === 'string' ? value : JSON.stringify(value ?? null);
|
|
77
|
+
const sha256 = createHash('sha256').update(raw).digest('hex');
|
|
78
|
+
return {
|
|
79
|
+
evidenceId: `ev_${sha256.slice(0, 16)}`,
|
|
80
|
+
kind,
|
|
81
|
+
at: new Date().toISOString(),
|
|
82
|
+
sha256,
|
|
83
|
+
preview: raw.slice(0, 500),
|
|
84
|
+
metadata,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function countInlineCognitionLenses(text) {
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
for (const match of String(text || '').matchAll(INLINE_LENS_RX)) {
|
|
91
|
+
const lens = match[0].match(/^\s*#\s*([a-z_ -]+)\s*:/i)?.[1]?.trim().toLowerCase();
|
|
92
|
+
if (lens) seen.add(lens);
|
|
93
|
+
}
|
|
94
|
+
return seen.size;
|
|
95
|
+
}
|
|
96
|
+
|
|
66
97
|
function resolveToken() {
|
|
67
98
|
if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
|
|
68
99
|
if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
|
|
@@ -111,7 +142,7 @@ async function getClient() {
|
|
|
111
142
|
return _client;
|
|
112
143
|
}
|
|
113
144
|
|
|
114
|
-
async function runtimeCheckAction(action, target) {
|
|
145
|
+
async function runtimeCheckAction(action, target, metadata = {}) {
|
|
115
146
|
const token = resolveToken();
|
|
116
147
|
if (!token) throw new Error('no token');
|
|
117
148
|
const response = await fetch(`${RUNTIME_URL}/check-action`, {
|
|
@@ -120,7 +151,7 @@ async function runtimeCheckAction(action, target) {
|
|
|
120
151
|
'Content-Type': 'application/json',
|
|
121
152
|
Authorization: `Bearer ${token}`,
|
|
122
153
|
},
|
|
123
|
-
body: JSON.stringify({ action, target }),
|
|
154
|
+
body: JSON.stringify({ action, target, ...metadata }),
|
|
124
155
|
});
|
|
125
156
|
if (!response.ok) {
|
|
126
157
|
const body = await response.text().catch(() => response.statusText);
|
|
@@ -163,9 +194,6 @@ export default async function HarnessGatePlugin(ctx) {
|
|
|
163
194
|
// Trivial reads pass
|
|
164
195
|
if (toolName === 'Bash' && TRIVIAL_BASH_RX.test(cmd) && cmd.length < 200) return;
|
|
165
196
|
if (toolName === 'Bash' && cmd.length < SHORT_BASH_LIMIT) return;
|
|
166
|
-
// Inline cognition in bash comments passes
|
|
167
|
-
if (toolName === 'Bash' && INLINE_LENS_RX.test(cmd)) return;
|
|
168
|
-
|
|
169
197
|
const destructive = DESTRUCTIVE_PATTERNS.find(({ rx }) => rx.test(cmd));
|
|
170
198
|
const deploy = DEPLOY_PATTERNS.find(({ rx }) => rx.test(cmd));
|
|
171
199
|
const isFileMutation = ['Edit', 'Write', 'NotebookEdit'].includes(toolName) && filePath;
|
|
@@ -178,6 +206,25 @@ export default async function HarnessGatePlugin(ctx) {
|
|
|
178
206
|
const label = destructive?.name || deploy?.name || `${toolName}:${filePath?.split('/').pop() || ''}`;
|
|
179
207
|
const action = toolName === 'Bash' ? 'bash' : 'edit';
|
|
180
208
|
const target = toolName === 'Bash' ? cmd.slice(0, 200) : filePath.slice(0, 200);
|
|
209
|
+
const skillGate = evaluateSkillGate({
|
|
210
|
+
sessionId,
|
|
211
|
+
surface: 'opencode-harness-gate',
|
|
212
|
+
text: JSON.stringify(input || {}),
|
|
213
|
+
action: cmd,
|
|
214
|
+
toolName,
|
|
215
|
+
filePath,
|
|
216
|
+
isDeploy: Boolean(deploy),
|
|
217
|
+
isMutation: Boolean(isFileMutation),
|
|
218
|
+
autoLoadAvailable: false,
|
|
219
|
+
});
|
|
220
|
+
if (!skillGate.ok) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`=== ARIA SKILL AUTOLOAD GATE: ${label} ===\n\n` +
|
|
223
|
+
`${formatSkillGateBlock(skillGate)}\n\n` +
|
|
224
|
+
`Required next step: call the skill loader for ${skillGate.missingSkills.join(', ')} before retrying this tool.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const actionRef = makeEvidenceRef('opencode_tool_request', { toolName, action, target }, { sessionId, label });
|
|
181
228
|
const rationale =
|
|
182
229
|
destructive ? `High-risk action ${label} requested in OpenCode and must satisfy Mizan truth, protection, and quality before execution.`
|
|
183
230
|
: deploy ? `Deployment action ${label} requested in OpenCode and must satisfy Mizan survivability before execution.`
|
|
@@ -188,10 +235,21 @@ export default async function HarnessGatePlugin(ctx) {
|
|
|
188
235
|
sessionId,
|
|
189
236
|
context: {
|
|
190
237
|
sessionId,
|
|
238
|
+
actor: 'opencode',
|
|
239
|
+
system: 'opencode',
|
|
240
|
+
platform: 'opencode',
|
|
241
|
+
surface: 'opencode-harness-gate',
|
|
191
242
|
message: cmdPreview,
|
|
192
243
|
intendedAction: target || cmdPreview,
|
|
193
244
|
rationale,
|
|
194
245
|
},
|
|
246
|
+
packetRequest: {
|
|
247
|
+
stage: 'preflight',
|
|
248
|
+
actor: 'opencode',
|
|
249
|
+
system: 'opencode',
|
|
250
|
+
platform: 'opencode',
|
|
251
|
+
message: cmdPreview,
|
|
252
|
+
},
|
|
195
253
|
});
|
|
196
254
|
_lastMizanReceipt = pre.receipt || null;
|
|
197
255
|
if (_lastMizanReceipt) {
|
|
@@ -202,6 +260,7 @@ export default async function HarnessGatePlugin(ctx) {
|
|
|
202
260
|
preResult: pre.result || null,
|
|
203
261
|
preContract: pre.contract || null,
|
|
204
262
|
preSummary: pre.summary || null,
|
|
263
|
+
actionRef,
|
|
205
264
|
});
|
|
206
265
|
}
|
|
207
266
|
_lastActionSummary = cmdPreview;
|
|
@@ -234,8 +293,25 @@ export default async function HarnessGatePlugin(ctx) {
|
|
|
234
293
|
|
|
235
294
|
if (client) {
|
|
236
295
|
try {
|
|
237
|
-
const check = await runtimeCheckAction(action, target
|
|
296
|
+
const check = await runtimeCheckAction(action, target, {
|
|
297
|
+
sessionId,
|
|
298
|
+
actor: 'opencode',
|
|
299
|
+
roleProfile: process.env.OPENCODE_ARIA_ROLE_PROFILE || process.env.ARIA_ROLE_PROFILE || null,
|
|
300
|
+
files: filePath ? [filePath] : [],
|
|
301
|
+
requireVerify: Boolean(destructive || deploy),
|
|
302
|
+
verifyText: String(input.args?.verifyText || input.args?.verifyBlock || input.args?.verify || ''),
|
|
303
|
+
}).catch(() => client.checkAction(action, target));
|
|
238
304
|
if (!check.allowed) {
|
|
305
|
+
if (check.queued) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`=== ARIA TOOL LANE QUEUED: ${label} ===\n\n` +
|
|
308
|
+
`Immediate execution paused; action was queued instead of failed.\n` +
|
|
309
|
+
`Reason: ${check.reason || 'tool lane contention'}\n` +
|
|
310
|
+
`Job: ${check.queue?.jobId || 'unknown'} (${check.queue?.status || 'queued'})\n` +
|
|
311
|
+
`Queue depth: ${check.queue?.queueDepth ?? 'unknown'}\n\n` +
|
|
312
|
+
`Recovery: retry after overlapping Hive lease expires, or let the tool-lane worker claim the queued job.`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
239
315
|
throw new Error(
|
|
240
316
|
`=== ARIA SD GATE: ${label} ===\n\n` +
|
|
241
317
|
`Blocked by substrate gate: ${check.reason || 'no reason provided'}\n` +
|
|
@@ -245,6 +321,7 @@ export default async function HarnessGatePlugin(ctx) {
|
|
|
245
321
|
}
|
|
246
322
|
return;
|
|
247
323
|
} catch (e) {
|
|
324
|
+
if (e.message.startsWith('=== ARIA TOOL LANE QUEUED')) throw e;
|
|
248
325
|
if (e.message.startsWith('=== ARIA SD GATE')) throw e;
|
|
249
326
|
// SDK unreachable — fall through to local gate below
|
|
250
327
|
process.stderr.write(`[harness-gate] SDK checkAction failed: ${e.message} — falling through to local gate\n`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../../../../ops/claude-hooks/lib/skill-autoload-gate.mjs';
|
|
@@ -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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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';
|