@aria_asi/cli 0.2.9 → 0.2.10

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.
@@ -206,10 +206,112 @@ const hasSubstrateEvidence = SUBSTRATE_EVIDENCE_RX.test(assistantText);
206
206
  const questionWithoutEvidence = hasQuestionToUser && !hasSubstrateEvidence;
207
207
 
208
208
  if (cog.count >= REQUIRED_LENSES) {
209
- audit('allow-cognition',
210
- `lenses=${cog.count} chars=${assistantText.length} ` +
211
- `qPatt=${hasQuestionToUser ? 'y' : 'n'} substrateEv=${hasSubstrateEvidence ? 'y' : 'n'} ` +
212
- (questionWithoutEvidence ? 'WARN-question-without-substrate' : 'ok'));
209
+ // ── Output-quality enforcement (Hamza 2026-04-27 — clients need the same
210
+ // Mizan/drift/code-quality gates that aria-soul applies server-side) ──
211
+ //
212
+ // Cognition gate passed. Now run THREE additional checks BEFORE allow:
213
+ // 1. SDK validateOutput via /api/harness/validate (Mizan classifier on draft)
214
+ // 2. Drift_guard pattern scan against doctrine_trigger_map.json (convenience-
215
+ // seeking phrases, graceful-degradation patterns, etc.)
216
+ // 3. Code-quality check on code blocks in output (no TODO stubs, no
217
+ // graceful-degradation try/catch, no // @ts-expect-error suppressions)
218
+ //
219
+ // Any check returning severity=block → Stop-gate blocks emit + Claude re-drafts
220
+ // with violations surfaced. Rewritten suggestion (from validateOutput) is
221
+ // included in the block reason so re-draft has concrete guidance.
222
+ //
223
+ // Trivially short outputs (<200 chars after system-reminder strip) skip
224
+ // these output-quality checks since they're typically yes/no acks where
225
+ // pattern-match would false-positive.
226
+ const OUTPUT_QC_MIN_CHARS = 200;
227
+ const OUTPUT_QC_ENABLED = (process.env.ARIA_OUTPUT_QC_ENABLED || 'true').toLowerCase() !== 'false';
228
+
229
+ if (OUTPUT_QC_ENABLED && assistantText.length >= OUTPUT_QC_MIN_CHARS) {
230
+ // 1. Drift_guard pattern scan — fast, local, deterministic
231
+ const TRIGGER_MAP_PATH = `${HOME}/.claude/projects/-home-hamzaibrahim1/memory/doctrine_trigger_map.json`;
232
+ let driftHits = [];
233
+ try {
234
+ if (existsSync(TRIGGER_MAP_PATH)) {
235
+ const triggerMap = JSON.parse(readFileSync(TRIGGER_MAP_PATH, 'utf8'));
236
+ const lowerText = assistantText.toLowerCase();
237
+ for (const t of triggerMap.triggers || []) {
238
+ try {
239
+ const rx = new RegExp(t.trigger, 'i');
240
+ if (rx.test(lowerText)) {
241
+ // Trigger present — check if the counter-doctrine memory is also
242
+ // cited in the response (justification). If not, count as drift.
243
+ const memoryName = (t.memory || '').replace(/\.md$/, '');
244
+ const memoryCited = memoryName && lowerText.includes(memoryName.toLowerCase());
245
+ if (!memoryCited) {
246
+ driftHits.push({ trigger: t.trigger, memory: t.memory, teaching: t.teaching });
247
+ }
248
+ }
249
+ } catch {/* malformed regex in trigger entry — skip */}
250
+ }
251
+ }
252
+ } catch {/* trigger map unreadable — degrade to mizan-only check */}
253
+
254
+ // 2. SDK validateOutput via /api/harness/validate (best-effort POST)
255
+ let mizanVerdict = null;
256
+ try {
257
+ const harnessUrl = process.env.ARIA_HARNESS_URL || 'https://harness.ariasos.com';
258
+ const harnessToken = process.env.ARIA_HARNESS_TOKEN || '';
259
+ const validateResp = await fetch(`${harnessUrl}/api/harness/validate`, {
260
+ method: 'POST',
261
+ headers: {
262
+ 'Content-Type': 'application/json',
263
+ ...(harnessToken ? { Authorization: `Bearer ${harnessToken}` } : {}),
264
+ },
265
+ body: JSON.stringify({
266
+ text: assistantText.slice(0, 8000),
267
+ sessionId: event.session_id || 'claude-code',
268
+ surface: 'claude-code-stop-gate',
269
+ }),
270
+ });
271
+ if (validateResp.ok) {
272
+ mizanVerdict = await validateResp.json();
273
+ }
274
+ } catch {/* network or endpoint unavailable — degrade to drift-only */}
275
+
276
+ // 3. Code-quality scan on code blocks
277
+ const codeBlocks = [...assistantText.matchAll(/```[a-z]*\n([\s\S]*?)```/gi)].map((m) => m[1]);
278
+ const codeQualityHits = [];
279
+ for (const block of codeBlocks) {
280
+ if (/\/\/\s*TODO|\/\/\s*FIXME|\/\/\s*XXX/.test(block)) codeQualityHits.push('TODO/FIXME/XXX in shipped code');
281
+ if (/@ts-expect-error|@ts-ignore/.test(block)) codeQualityHits.push('ts-expect-error / ts-ignore — type suppression instead of fix');
282
+ if (/catch\s*\([^)]*\)\s*\{\s*(?:return\s+(?:''|""|null|undefined|\[\]|\{\})|\}\s*$|\/\/[^\n]*$)/m.test(block)) codeQualityHits.push('catch block with empty/silent fallthrough — graceful degradation');
283
+ if (/console\.log\(/.test(block) && !/\/\/\s*debug|\/\/\s*log/i.test(block)) codeQualityHits.push('console.log in shipped code without debug/log comment');
284
+ }
285
+
286
+ // Block decision: any of (validateOutput severity=block) OR (>=2 drift hits) OR
287
+ // (>=1 code-quality hit) → block emit, surface violations + rewritten suggestion.
288
+ const mizanBlock = mizanVerdict && mizanVerdict.severity === 'block';
289
+ const driftBlock = driftHits.length >= 2;
290
+ const codeBlock = codeQualityHits.length >= 1;
291
+
292
+ if (mizanBlock || driftBlock || codeBlock) {
293
+ const violations = [];
294
+ if (mizanBlock) violations.push(`Mizan: ${(mizanVerdict.violations || []).join(', ')}`);
295
+ if (driftBlock) violations.push(`Drift triggers (${driftHits.length}): ${driftHits.map((h) => `"${h.trigger}" → ${h.memory}`).join(' | ')}`);
296
+ if (codeBlock) violations.push(`Code quality: ${codeQualityHits.join('; ')}`);
297
+ const rewritten = mizanVerdict?.rewritten || '';
298
+
299
+ const reason = `Aria Stop-gate output-quality block. Cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates:\n\n${violations.join('\n\n')}${rewritten ? `\n\nMizan rewrite suggestion:\n${rewritten}` : ''}\n\nRe-draft addressing the violations above. ARIA_OUTPUT_QC_ENABLED=false to disable in emergency (logged).`;
300
+
301
+ audit(`block-output-qc`, `mizan=${mizanBlock?'y':'n'} drift=${driftHits.length} code=${codeQualityHits.length}`);
302
+ console.log(JSON.stringify({ decision: 'block', reason }));
303
+ process.exit(2);
304
+ }
305
+
306
+ audit('allow-output-qc',
307
+ `lenses=${cog.count} chars=${assistantText.length} drift=${driftHits.length} ` +
308
+ `mizan=${mizanVerdict ? mizanVerdict.severity : 'unavailable'} code=${codeQualityHits.length}`);
309
+ } else {
310
+ audit('allow-cognition',
311
+ `lenses=${cog.count} chars=${assistantText.length} ` +
312
+ `qPatt=${hasQuestionToUser ? 'y' : 'n'} substrateEv=${hasSubstrateEvidence ? 'y' : 'n'} ` +
313
+ (questionWithoutEvidence ? 'WARN-question-without-substrate' : 'ok'));
314
+ }
213
315
  process.exit(0);
214
316
  }
215
317
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aria_asi/cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "Aria Smart CLI — the world's first harness-powered terminal companion",
5
5
  "bin": {
6
6
  "aria": "./bin/aria.js"