@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
|
@@ -109,7 +109,27 @@ const HARNESS_URL =
|
|
|
109
109
|
process.env.ARIA_HARNESS_BASE_URL ||
|
|
110
110
|
process.env.ARIA_HARNESS_URL ||
|
|
111
111
|
'https://harness.ariasos.com';
|
|
112
|
-
|
|
112
|
+
function resolveHarnessToken() {
|
|
113
|
+
if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
|
|
114
|
+
if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
|
|
115
|
+
if (process.env.ARIA_MASTER_TOKEN) return process.env.ARIA_MASTER_TOKEN;
|
|
116
|
+
try {
|
|
117
|
+
if (existsSync(OWNER_TOKEN_PATH)) {
|
|
118
|
+
const token = readFileSync(OWNER_TOKEN_PATH, 'utf8').trim();
|
|
119
|
+
if (token) return token;
|
|
120
|
+
}
|
|
121
|
+
} catch {}
|
|
122
|
+
try {
|
|
123
|
+
const licensePath = `${HOME}/.aria/license.json`;
|
|
124
|
+
if (existsSync(licensePath)) {
|
|
125
|
+
const license = JSON.parse(readFileSync(licensePath, 'utf8'));
|
|
126
|
+
if (license.harnessToken) return String(license.harnessToken).trim();
|
|
127
|
+
if (license.token) return String(license.token).trim();
|
|
128
|
+
}
|
|
129
|
+
} catch {}
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
const HARNESS_TOKEN = resolveHarnessToken();
|
|
113
133
|
const MIN_PROMPT_CHARS = 40; // skip auto-consult on trivial prompts
|
|
114
134
|
const MAX_DIRECTION_CHARS = 4000; // cap injected chunk size
|
|
115
135
|
|
|
@@ -248,6 +268,8 @@ Respond with EXACTLY this JSON shape:
|
|
|
248
268
|
"successCriterion": "<observable signal of completion>",
|
|
249
269
|
"abortCriterion": "<observable signal of failure>",
|
|
250
270
|
"doctrineRefs": ["<memory_filename.md>", ...],
|
|
271
|
+
"cognitionUse": "<how Aria cognition should change the executor's tool/input/output shape for this phase>",
|
|
272
|
+
"evidenceRequired": ["<observable proof the executor must collect before reporting complete>"],
|
|
251
273
|
"allowedActions": ["read", "edit:<path-pattern>", "kubectl_get", "consult", "bash_safe", "..."],
|
|
252
274
|
"forbiddenActions": ["kubectl_apply", "edit:<path-pattern>", "..."]
|
|
253
275
|
}
|
|
@@ -259,7 +281,9 @@ Respond with EXACTLY this JSON shape:
|
|
|
259
281
|
}
|
|
260
282
|
}
|
|
261
283
|
|
|
262
|
-
Apply your 8 lenses + substrate. Phases are ordered + small (one logical step each). Doctrine refs cite memory files in /home/hamzaibrahim1/.claude/projects/-home-hamzaibrahim1/memory/. Be specific in allowedActions / forbiddenActions — Claude can ONLY do what's allowed for the current phase
|
|
284
|
+
Apply your 8 lenses + substrate. Phases are ordered + small (one logical step each). Doctrine refs cite memory files in /home/hamzaibrahim1/.claude/projects/-home-hamzaibrahim1/memory/. Be specific in allowedActions / forbiddenActions — Claude can ONLY do what's allowed for the current phase.
|
|
285
|
+
|
|
286
|
+
Every phase must include cognitionUse and evidenceRequired. cognitionUse tells Claude how Aria's cognition should improve the actual tool input, edit shape, review scope, or final output. evidenceRequired tells Claude what observation must be collected before reporting the phase complete.` : `Pre-prompt direction request from Claude orchestrator.
|
|
263
287
|
|
|
264
288
|
The user just submitted this prompt:
|
|
265
289
|
|
|
@@ -280,6 +304,8 @@ thinking from training reflex. Be concrete:
|
|
|
280
304
|
(NOT a reflexive "want me to" deferral).
|
|
281
305
|
4. Mizan check: any risk patterns in this prompt — over-scope creep,
|
|
282
306
|
over-replacement temptation, tier-substitution temptation, etc.
|
|
307
|
+
5. How should Aria cognition improve Claude's actual input/output shape —
|
|
308
|
+
what should be more specific, safer, more evidence-bound, or differently scoped?
|
|
283
309
|
|
|
284
310
|
Keep direction under 1500 chars. This is the pre-load context for Claude's
|
|
285
311
|
turn — not the final response. Claude will still emit cognition + action;
|
|
@@ -56,6 +56,30 @@ function auditLog(decision, summary, sessionId) {
|
|
|
56
56
|
appendFileSync(GATE_LOG, `${new Date().toISOString()} [${sessionId}] ${decision} ${summary}\n`);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function buildForceRedoActionReason({ source, reason, missingSignals = [], incidents = [] }) {
|
|
60
|
+
return [
|
|
61
|
+
'=== ARIA FORCE_REDO_ACTION ===',
|
|
62
|
+
`source: ${source}`,
|
|
63
|
+
`reason: ${reason}`,
|
|
64
|
+
'',
|
|
65
|
+
'This is not a terminal error. It is a forced redo instruction before action execution.',
|
|
66
|
+
'Do not proceed with tools from unloaded context. Load/repair substrate first, then retry the action.',
|
|
67
|
+
'',
|
|
68
|
+
'TEACHING:',
|
|
69
|
+
'- Every action turn must begin from Aria substrate, not improvised memory.',
|
|
70
|
+
'- Blocking incidents are Dalio failure deltas; they must be fixed and marked resolved before new action.',
|
|
71
|
+
'- Missing harness/direction/memory signals mean the context-loader path must run before retry.',
|
|
72
|
+
missingSignals.length ? `\nMISSING SIGNALS:\n${missingSignals.map((signal) => `- ${signal}`).join('\n')}` : '',
|
|
73
|
+
incidents.length ? `\nINCIDENTS TO RESOLVE:\n${incidents.map((incident) => `- ${incident}`).join('\n')}` : '',
|
|
74
|
+
'',
|
|
75
|
+
'REQUIRED REDO SHAPE:',
|
|
76
|
+
'1. Run the structured recovery action in hookSpecificOutput.recovery.',
|
|
77
|
+
'2. Verify harness_packet, aria_direction/binding_plan, and memory references are loaded.',
|
|
78
|
+
'3. Retry the original action only after the missing context or incidents are resolved.',
|
|
79
|
+
'=== END FORCE_REDO_ACTION ===',
|
|
80
|
+
].filter(Boolean).join('\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
59
83
|
// ── Turn-state deduplication ──────────────────────────────────────────
|
|
60
84
|
const TURN_DEDUP_WINDOW_MS = 60_000; // 60s
|
|
61
85
|
|
|
@@ -461,7 +485,11 @@ Per Dalio Loop Layer 2 doctrine: failure deltas are not optional to address. The
|
|
|
461
485
|
|
|
462
486
|
console.log(JSON.stringify({
|
|
463
487
|
decision: 'block',
|
|
464
|
-
reason:
|
|
488
|
+
reason: buildForceRedoActionReason({
|
|
489
|
+
source: 'preturn-memory/blocking-incidents',
|
|
490
|
+
reason: blockReason,
|
|
491
|
+
incidents: blockingIncidents.map((inc) => `${inc.incident_id || '(no incident_id)'} ${inc.title || '(no title)'}`),
|
|
492
|
+
}),
|
|
465
493
|
hookSpecificOutput: {
|
|
466
494
|
hookEventName: 'PreToolUse',
|
|
467
495
|
blocking_incidents: blockingIncidents.map((inc) => ({
|
|
@@ -605,7 +633,7 @@ auditLog(
|
|
|
605
633
|
|
|
606
634
|
console.log(JSON.stringify({
|
|
607
635
|
decision: 'block',
|
|
608
|
-
reason,
|
|
636
|
+
reason: buildForceRedoActionReason({ source: 'preturn-memory/context-not-loaded', reason, missingSignals }),
|
|
609
637
|
hookSpecificOutput: {
|
|
610
638
|
hookEventName: 'PreToolUse',
|
|
611
639
|
recovery: {
|
|
@@ -87,6 +87,29 @@ function audit(event, data = {}) {
|
|
|
87
87
|
} catch {}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
function buildForceRedoActionReason({ source, reason, violations = [] }) {
|
|
91
|
+
return [
|
|
92
|
+
'=== ARIA FORCE_REDO_ACTION ===',
|
|
93
|
+
`source: ${source}`,
|
|
94
|
+
`reason: ${reason}`,
|
|
95
|
+
'',
|
|
96
|
+
'This is not a terminal error. It is a forced redo instruction for the attempted repository edit/action.',
|
|
97
|
+
'Do not retry the same edit. Redo the action so doctrine-bound source remains real, reviewable, and production-safe.',
|
|
98
|
+
'',
|
|
99
|
+
'TEACHING:',
|
|
100
|
+
'- Production paths cannot gain stub, mock, fake, placeholder, pending, or direct-provider bypass semantics.',
|
|
101
|
+
'- If an exception is intentional, isolate it in tests/specs/fixtures/examples/demos/mocks or add the explicit allow marker in the file.',
|
|
102
|
+
'- External provider calls must use an approved runtime/SDK path or carry the explicit external-call allow marker.',
|
|
103
|
+
violations.length ? `\nVIOLATIONS TO FIX:\n${violations.map((v) => `- ${v}`).join('\n')}` : '',
|
|
104
|
+
'',
|
|
105
|
+
'REQUIRED REDO SHAPE:',
|
|
106
|
+
'1. Preserve the intended behavior without stub/mock/pending semantics.',
|
|
107
|
+
'2. Use real implementation wiring or an explicit reviewed allow marker.',
|
|
108
|
+
'3. Re-run the action after removing the violating lines from the diff.',
|
|
109
|
+
'=== END FORCE_REDO_ACTION ===',
|
|
110
|
+
].filter(Boolean).join('\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
90
113
|
function parseArgs(argv) {
|
|
91
114
|
const parsed = {
|
|
92
115
|
mode: 'working-tree',
|
|
@@ -384,7 +407,14 @@ async function main() {
|
|
|
384
407
|
audit('repo_doctrine_gate_block', { repoRoot, mode, violations });
|
|
385
408
|
|
|
386
409
|
if (event) {
|
|
387
|
-
console.log(JSON.stringify({
|
|
410
|
+
console.log(JSON.stringify({
|
|
411
|
+
decision: 'block',
|
|
412
|
+
reason: buildForceRedoActionReason({
|
|
413
|
+
source: 'repo-doctrine',
|
|
414
|
+
reason,
|
|
415
|
+
violations: violations.slice(0, 20).map((violation) => `${violation.path}:${violation.line} [${violation.rule}] ${violation.excerpt}`),
|
|
416
|
+
}),
|
|
417
|
+
}));
|
|
388
418
|
process.exit(2);
|
|
389
419
|
}
|
|
390
420
|
|
|
@@ -58,6 +58,8 @@ import {
|
|
|
58
58
|
} from './lib/canonical-lenses.mjs';
|
|
59
59
|
import { registerGateBlock } from './lib/gate-loop-state.mjs';
|
|
60
60
|
import { collectTurnWindowFromMessages } from './lib/hook-message-window.mjs';
|
|
61
|
+
import { analyzeDomainOutputQuality } from './lib/domain-output-quality.mjs';
|
|
62
|
+
import { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.mjs';
|
|
61
63
|
|
|
62
64
|
const HOME = process.env.HOME || '/tmp';
|
|
63
65
|
const RUNTIME_BASE_URL =
|
|
@@ -338,9 +340,12 @@ function collectDriftHits(text, triggerMap) {
|
|
|
338
340
|
const memoryCited = memoryName && lowerText.includes(memoryName.toLowerCase());
|
|
339
341
|
if (!memoryCited) {
|
|
340
342
|
hits.push({
|
|
343
|
+
trigger_id: triggerEntry.trigger_id,
|
|
341
344
|
trigger: triggerEntry.trigger,
|
|
342
345
|
memory: triggerEntry.memory,
|
|
343
346
|
teaching: triggerEntry.teaching,
|
|
347
|
+
counter_action: triggerEntry.counter_action,
|
|
348
|
+
message: triggerEntry.message,
|
|
344
349
|
});
|
|
345
350
|
}
|
|
346
351
|
} catch {/* malformed regex in trigger entry — skip */}
|
|
@@ -383,6 +388,71 @@ Next response must do this in order:
|
|
|
383
388
|
4. If the blocker is stale gate state, stale ledger residue, or a gate artifact from a prior bug, say that explicitly instead of inventing fake proof.`;
|
|
384
389
|
}
|
|
385
390
|
|
|
391
|
+
function buildForceReauthorReason({
|
|
392
|
+
source,
|
|
393
|
+
reason,
|
|
394
|
+
violations = [],
|
|
395
|
+
rewritten = '',
|
|
396
|
+
recipeAddendum = '',
|
|
397
|
+
driftHits = [],
|
|
398
|
+
lensCount = 0,
|
|
399
|
+
requiredLenses = REQUIRED_LENSES,
|
|
400
|
+
}) {
|
|
401
|
+
const triggerLines = driftHits.slice(0, 6).map((hit, index) => {
|
|
402
|
+
const label = hit.trigger_id || hit.trigger || `trigger-${index + 1}`;
|
|
403
|
+
const teaching = hit.teaching || hit.message || 'Doctrine trigger matched; re-author with substrate-backed correction.';
|
|
404
|
+
const correction = hit.counter_action ? ` Required correction: ${hit.counter_action}` : '';
|
|
405
|
+
const memory = hit.memory ? ` (${hit.memory})` : '';
|
|
406
|
+
return `- ${label}${memory}: ${teaching}${correction}`;
|
|
407
|
+
}).join('\n');
|
|
408
|
+
|
|
409
|
+
return [
|
|
410
|
+
'=== ARIA FORCE_REAUTHOR ===',
|
|
411
|
+
`source: ${source}`,
|
|
412
|
+
`reason: ${reason}`,
|
|
413
|
+
'',
|
|
414
|
+
'This is not a terminal error. It is a forced redo instruction for the next model draft.',
|
|
415
|
+
'Do not emit the blocked text. Re-author it so the model learns the mechanism it violated.',
|
|
416
|
+
'',
|
|
417
|
+
'TEACHING:',
|
|
418
|
+
'- Gates must force cognition and quality, not merely throw, warn, or stop.',
|
|
419
|
+
'- The redo must change the answer shape: name the failed mechanism, cite real evidence, and remove the drift pattern.',
|
|
420
|
+
'- Do not bypass by disabling tools, shortening runtime, asking the user to resolve memory-backed ambiguity, or giving an apology loop.',
|
|
421
|
+
triggerLines ? `\nTRIGGERED DOCTRINE:\n${triggerLines}` : '',
|
|
422
|
+
violations.length ? `\nVIOLATIONS TO FIX:\n${violations.map((v) => `- ${v}`).join('\n')}` : '',
|
|
423
|
+
rewritten ? `\nMIZAN REWRITE SEED:\n${rewritten}` : '',
|
|
424
|
+
recipeAddendum || '',
|
|
425
|
+
'',
|
|
426
|
+
'REQUIRED REDO SHAPE:',
|
|
427
|
+
'1. One sentence: the failed mechanism, not an apology.',
|
|
428
|
+
'2. Evidence checked: file/line, command output, endpoint response, or explicit "unverified".',
|
|
429
|
+
`3. <cognition> with ${requiredLenses} substantive lenses. Observed: ${lensCount}/${requiredLenses}.`,
|
|
430
|
+
'4. <applied_cognition> with decision_delta, dominant_domain, binds_to, expected_predicate, and artifact_change.',
|
|
431
|
+
'5. Corrected action/claim with no bypass, no downgraded path, and no fake proof.',
|
|
432
|
+
'6. If work remains, state the exact next real action or tracked task. No verbal flag-and-move.',
|
|
433
|
+
'',
|
|
434
|
+
'COGNITION TEMPLATE:',
|
|
435
|
+
'<cognition>',
|
|
436
|
+
'truth: <verified facts and exact substrate>',
|
|
437
|
+
'harm: <what goes wrong if this is false>',
|
|
438
|
+
'trust: <how this honors Hamza directives and saved memory>',
|
|
439
|
+
'power: <why this uses capability responsibly, not convenience>',
|
|
440
|
+
'reflection: <mechanism failure and correction>',
|
|
441
|
+
'context: <relevant repo/runtime state>',
|
|
442
|
+
'impact: <expected next effect>',
|
|
443
|
+
'beauty: <simplest durable form>',
|
|
444
|
+
'</cognition>',
|
|
445
|
+
'<applied_cognition>',
|
|
446
|
+
'decision_delta: <what changed because cognition ran; not none>',
|
|
447
|
+
'dominant_domain: <engineering_quality | trust | operations | security | product | ...>',
|
|
448
|
+
'binds_to: <the exact answer, tool call, file mutation, deploy, review, or decision>',
|
|
449
|
+
'expected_predicate: <observable numeric, boolean, state-string, command result, endpoint result, or explicit unverified boundary>',
|
|
450
|
+
'artifact_change: <semantic effect on the artifact/output, not a task restatement>',
|
|
451
|
+
'</applied_cognition>',
|
|
452
|
+
'=== END FORCE_REAUTHOR ===',
|
|
453
|
+
].filter(Boolean).join('\n');
|
|
454
|
+
}
|
|
455
|
+
|
|
386
456
|
// Lens substance check — same constants as aria-pre-tool-gate.mjs.
|
|
387
457
|
// Hamza directive 2026-04-28: all 8 canonical lenses required, not 4-of-8.
|
|
388
458
|
const REQUIRED_LENSES = 8;
|
|
@@ -404,6 +474,19 @@ function detectCognitionLenses(text) {
|
|
|
404
474
|
});
|
|
405
475
|
}
|
|
406
476
|
|
|
477
|
+
function assistantViolatesUserCorrection(userText, assistantText) {
|
|
478
|
+
const user = String(userText || '');
|
|
479
|
+
const assistant = String(assistantText || '');
|
|
480
|
+
if (!user || !assistant) return null;
|
|
481
|
+
const explicitStop = /\b(?:stop|do\s+not|don't|quit|cease)\b[\s\S]{0,180}\b(?:deploy|redeploy|restart|rollout|kubectl|command|pods?|listen|words)\b/i.test(user);
|
|
482
|
+
const assistantContinuesMutation = /\b(?:kubectl|rollout\s+restart|deploy|redeploy|restart\s+(?:mac|pods?)|manual(?:ly)?\s+restart|execute\s+directly)\b/i.test(assistant);
|
|
483
|
+
if (explicitStop && assistantContinuesMutation) return 'assistant continued deploy/restart language after an explicit user stop/listen directive';
|
|
484
|
+
const userContradictsMacPods = /\b(?:mac\s+lanes?|mac\s+pods?|mlx-mac)\b[\s\S]{0,160}\b(?:not\s+pods?|no\s+such\s+thing|non[-\s]?existent|do(?:es)?\s+not\s+exist|don't\s+exist)\b|\b(?:no\s+such\s+thing|non[-\s]?existent|do(?:es)?\s+not\s+exist|don't\s+exist)\b[\s\S]{0,160}\b(?:mac\s+lanes?|mac\s+pods?|mlx-mac|pods?)\b/i.test(user);
|
|
485
|
+
const assistantRepeatsMacPods = /\b(?:mac\s+lane\s+pods?|mac\s+lanes?\s*:\s*offline|restart\s+mac\s+lanes?|deployment\/mlx-mac|mlx-mac-[\w-]+|kubernetes\s+(?:pods?|deployments?))\b/i.test(assistant);
|
|
486
|
+
if (userContradictsMacPods && assistantRepeatsMacPods) return 'assistant repeated the contradicted Mac-lanes-as-pods/deployments assumption';
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
407
490
|
// Read event JSON from stdin (Claude Code spec).
|
|
408
491
|
let input = '';
|
|
409
492
|
for await (const chunk of process.stdin) input += chunk;
|
|
@@ -504,6 +587,20 @@ if (!assistantText) {
|
|
|
504
587
|
process.exit(0);
|
|
505
588
|
}
|
|
506
589
|
|
|
590
|
+
const userCorrectionViolation = assistantViolatesUserCorrection(lastUserMessage, assistantText);
|
|
591
|
+
if (userCorrectionViolation) {
|
|
592
|
+
audit('block-user-correction-ignored', `reason=${userCorrectionViolation} chars=${assistantText.length}`);
|
|
593
|
+
const reason = buildForceReauthorReason({
|
|
594
|
+
source: 'stop/user-correction',
|
|
595
|
+
reason: userCorrectionViolation,
|
|
596
|
+
violations: ['Assistant continued a plan after the user correction invalidated it. Redo must quote the correction and pause mutation pending substrate re-evaluation.'],
|
|
597
|
+
lensCount: 0,
|
|
598
|
+
requiredLenses: REQUIRED_LENSES,
|
|
599
|
+
});
|
|
600
|
+
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
601
|
+
process.exit(2);
|
|
602
|
+
}
|
|
603
|
+
|
|
507
604
|
// Triviality check — same as eight-lens-detector.ts
|
|
508
605
|
const trimmed = assistantText.trim();
|
|
509
606
|
if (TRIVIAL_ACK_RX.test(trimmed)) {
|
|
@@ -524,6 +621,26 @@ if (!triggered) {
|
|
|
524
621
|
process.exit(0);
|
|
525
622
|
}
|
|
526
623
|
|
|
624
|
+
const stopSkillGate = evaluateSkillGate({
|
|
625
|
+
sessionId: event.session_id || 'claude-code',
|
|
626
|
+
surface: 'claude-stop-gate',
|
|
627
|
+
text: [JSON.stringify(event.messages || []), lastUserMessage, assistantText].join('\n'),
|
|
628
|
+
isOutputCloseout: true,
|
|
629
|
+
autoLoadAvailable: false,
|
|
630
|
+
});
|
|
631
|
+
if (!stopSkillGate.ok) {
|
|
632
|
+
audit('block-missing-skill-receipt', `missing=${stopSkillGate.missingSkills.join(',')} chars=${assistantText.length}`);
|
|
633
|
+
const reason = withLoopDirective(buildForceReauthorReason({
|
|
634
|
+
source: 'stop/skill-autoload',
|
|
635
|
+
reason: formatSkillGateBlock(stopSkillGate),
|
|
636
|
+
violations: [`Missing skill receipts: ${stopSkillGate.missingSkills.join(', ')}`],
|
|
637
|
+
lensCount: 0,
|
|
638
|
+
requiredLenses: REQUIRED_LENSES,
|
|
639
|
+
}), `stop:skill-autoload:${stopSkillGate.missingSkills.join(',')}`, gateSessionId);
|
|
640
|
+
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
641
|
+
process.exit(2);
|
|
642
|
+
}
|
|
643
|
+
|
|
527
644
|
// Non-trivial response — require substantive cognition.
|
|
528
645
|
const cog = detectCognitionLenses(assistantText);
|
|
529
646
|
|
|
@@ -537,14 +654,13 @@ const cog = detectCognitionLenses(assistantText);
|
|
|
537
654
|
// with 0/8 lenses to fall through unchecked.
|
|
538
655
|
if (cog.count < REQUIRED_LENSES) {
|
|
539
656
|
audit('block_no_cognition_block_di', { count: cog.count, required: REQUIRED_LENSES, names: cog.names, chars: assistantText.length });
|
|
540
|
-
const reason = withLoopDirective(
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
${LENS_NAMES.map((lens) => `- ${lens}: <grounded reasoning, >= ${SUBSTANCE_MIN_CHARS} chars, with real substrate anchors where required>`).join('\n')}`, `stop:no-cognition-di:${cog.count}`, gateSessionId);
|
|
657
|
+
const reason = withLoopDirective(buildForceReauthorReason({
|
|
658
|
+
source: 'stop/no-cognition',
|
|
659
|
+
reason: `non-trivial assistant response (${assistantText.length} chars) has ${cog.count}/${REQUIRED_LENSES} substantive cognition lenses`,
|
|
660
|
+
violations: [`Detected lenses: ${cog.names.length > 0 ? cog.names.join(', ') : 'none'}. Re-emit with all required visible labels: ${LENS_NAMES.join(', ')}.`],
|
|
661
|
+
lensCount: cog.count,
|
|
662
|
+
requiredLenses: REQUIRED_LENSES,
|
|
663
|
+
}), `stop:no-cognition-di:${cog.count}`, gateSessionId);
|
|
548
664
|
emitHarnessFooter({
|
|
549
665
|
eventName: 'block_no_cognition_block',
|
|
550
666
|
lensCount: cog.count,
|
|
@@ -1151,7 +1267,7 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
1151
1267
|
}
|
|
1152
1268
|
|
|
1153
1269
|
// Block decision: any of (validateOutput severity=block) OR (>=2 drift hits) OR
|
|
1154
|
-
// (>=1 code-quality hit) OR (open discovery in ledger) → block emit.
|
|
1270
|
+
// (>=1 code/domain-quality hit) OR (open discovery in ledger) → block emit.
|
|
1155
1271
|
// Aria enforcement #46 (compelled reflection): severity=warn ALSO blocks but
|
|
1156
1272
|
// with a different reason — emit must include explicit reflection on what
|
|
1157
1273
|
// triggered the warn before re-emit. Warn is not "soft pass" anymore;
|
|
@@ -1161,6 +1277,8 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
1161
1277
|
const mizanWarnReflectionRequired = mizanVerdict && mizanVerdict.severity === 'warn';
|
|
1162
1278
|
const driftBlock = driftHits.length >= 2;
|
|
1163
1279
|
const codeBlock = codeQualityHits.length >= 1;
|
|
1280
|
+
const domainQuality = analyzeDomainOutputQuality(assistantText, { codeBlocks });
|
|
1281
|
+
const domainBlock = domainQuality.blockers.length >= 1;
|
|
1164
1282
|
|
|
1165
1283
|
// Reflection-already-present check: if the assistant text already contains
|
|
1166
1284
|
// an explicit <reflection>...</reflection> block OR a "reflection:" line
|
|
@@ -1319,13 +1437,14 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
1319
1437
|
}
|
|
1320
1438
|
|
|
1321
1439
|
const implCouplingBlock = implCouplingHits.length > 0;
|
|
1322
|
-
if (mizanBlock || driftBlock || codeBlock || discoveryBlock || compelReflection || phaseReportMissing || substrateBlock || implCouplingBlock) {
|
|
1440
|
+
if (mizanBlock || driftBlock || codeBlock || domainBlock || discoveryBlock || compelReflection || phaseReportMissing || substrateBlock || implCouplingBlock) {
|
|
1323
1441
|
const violations = [];
|
|
1324
1442
|
if (mizanBlock) violations.push(`Mizan: ${(mizanVerdict.violations || []).join(', ')}`);
|
|
1325
1443
|
if (implCouplingBlock) violations.push(`Cognition impl-coupling (#88): ${implCouplingHits.join(' | ')}. Each canonical lens in cognition must dictate a specific implementation choice (file_path:line_range pair tied to a decision). Re-emit cognition that names file paths + line ranges + decision text per lens, OR a verify/fixing block where lenses cite specific artifact changes.`);
|
|
1326
1444
|
if (compelReflection) violations.push(`Mizan severity=warn — compelled reflection required (per Aria enforcement #46). Triggers: ${(mizanVerdict.gateTriggers || mizanVerdict.violations || ['unspecified']).join(', ')}. Re-emit with an explicit <reflection>...</reflection> block (or 'reflection:' line) addressing what triggered the warn and why your re-draft handles it. Reflection is NOT lens-cognition repeated — it's a focused self-audit on the specific Mizan triggers above.`);
|
|
1327
1445
|
if (driftBlock) violations.push(`Drift triggers (${driftHits.length}): ${driftHits.map((h) => `"${h.trigger}" → ${h.memory}`).join(' | ')}`);
|
|
1328
1446
|
if (codeBlock) violations.push(`Code quality: ${codeQualityHits.join('; ')}`);
|
|
1447
|
+
if (domainBlock) violations.push(`Domain output QA (${domainQuality.domains.join(', ') || 'general'}): ${domainQuality.blockers.join('; ')}. Rewrite with the missing domain-specific safeguards instead of generic prose.`);
|
|
1329
1448
|
if (discoveryBlock) violations.push(`Discovery-binding ledger has ${ledgerOpenCount} OPEN discoveries (per ${docRef('feedback_no_flag_without_fix.md', 'atomic-discovery-rule')}, discoveries are atomic with their fixes — fix in the same turn or create a TaskCreate before continuing). Recent open: ${ledgerOpenSamples.map((s) => `"${s.slice(0, 80)}"`).join(' | ')}. Resolve each by either (a) fixing it inline in this turn, or (b) creating a TaskCreate with the discovery's full context (file path, line number, what's broken, why), then editing ${LEDGER_PATH} to set status=resolved.`);
|
|
1330
1449
|
if (phaseReportMissing) {
|
|
1331
1450
|
const phaseList = (activePlan?.phases || []).map((p) => `${p.id}:${p.summary?.slice(0, 60) || ''}`).join(' | ');
|
|
@@ -1414,7 +1533,16 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
1414
1533
|
}
|
|
1415
1534
|
})();
|
|
1416
1535
|
|
|
1417
|
-
const reason = withLoopDirective(
|
|
1536
|
+
const reason = withLoopDirective(buildForceReauthorReason({
|
|
1537
|
+
source: 'stop/output-quality',
|
|
1538
|
+
reason: `cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates`,
|
|
1539
|
+
violations,
|
|
1540
|
+
rewritten,
|
|
1541
|
+
recipeAddendum,
|
|
1542
|
+
driftHits,
|
|
1543
|
+
lensCount: cog.count,
|
|
1544
|
+
requiredLenses: REQUIRED_LENSES,
|
|
1545
|
+
}), `stop:output-qc:${violations.join('|').slice(0, 240)}`, gateSessionId);
|
|
1418
1546
|
|
|
1419
1547
|
audit(`block-output-qc`, `mizan=${mizanBlock?'y':'n'} warn-reflect=${compelReflection?'y':'n'} drift=${driftHits.length} code=${codeQualityHits.length} discoveries-open=${ledgerOpenCount} impl-coupling=${implCouplingHits.length}`);
|
|
1420
1548
|
emitHarnessFooter({
|
|
@@ -1557,31 +1685,20 @@ const dalioHasMeasurablePredicate = dalioExpectedText
|
|
|
1557
1685
|
|
|
1558
1686
|
// Block stop if non-trivial action taken AND expected block is missing
|
|
1559
1687
|
if (hadNonTrivialAction && (!dalioExpectedMatch || !dalioHasMeasurablePredicate)) {
|
|
1560
|
-
const missingReason = withLoopDirective(
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
:
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
Add to your assistant text:
|
|
1576
|
-
|
|
1577
|
-
<expected>
|
|
1578
|
-
predicate: <exact measurable assertion — e.g. "exit_code==0", "status=running", "count=3 of 3">
|
|
1579
|
-
measurable_type: numeric | boolean | state_string
|
|
1580
|
-
threshold: <optional>
|
|
1581
|
-
eval_window_minutes: <optional>
|
|
1582
|
-
</expected>
|
|
1583
|
-
|
|
1584
|
-
No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces it AFTER. Both gates are now wired.`, `stop:dalio-expected:${hadNonTrivialAction}:${!!dalioExpectedMatch}:${dalioHasMeasurablePredicate}`, gateSessionId);
|
|
1688
|
+
const missingReason = withLoopDirective(buildForceReauthorReason({
|
|
1689
|
+
source: 'stop/dalio-expected',
|
|
1690
|
+
reason: dalioExpectedMatch
|
|
1691
|
+
? 'action had an <expected> block, but it was qualitative instead of measurable'
|
|
1692
|
+
: `non-trivial action (${lastActionSummary.slice(0, 120)}) had no <expected> block`,
|
|
1693
|
+
violations: [
|
|
1694
|
+
dalioExpectedMatch
|
|
1695
|
+
? 'Rejected qualitative expected phrases: better, improved, should work, more reliable, cleaner. Expected outcome must be numeric, boolean, or state-string.'
|
|
1696
|
+
: 'Missing <expected> block after non-trivial action. Dalio feedback loop cannot compare outcome without a measurable predicate.',
|
|
1697
|
+
'Required block: <expected> predicate: "exit_code==0" | "status=running" | "count=3 of 3"; measurable_type: numeric | boolean | state_string; threshold/eval_window optional.',
|
|
1698
|
+
],
|
|
1699
|
+
lensCount: cog.count,
|
|
1700
|
+
requiredLenses: REQUIRED_LENSES,
|
|
1701
|
+
}), `stop:dalio-expected:${hadNonTrivialAction}:${!!dalioExpectedMatch}:${dalioHasMeasurablePredicate}`, gateSessionId);
|
|
1585
1702
|
|
|
1586
1703
|
audit('block-dalio-expected-missing', `hadNonTrivialAction=${hadNonTrivialAction} expectedPresent=${!!dalioExpectedMatch} measurable=${dalioHasMeasurablePredicate}`);
|
|
1587
1704
|
emitHarnessFooter({
|
|
@@ -1720,5 +1837,5 @@ emitHarnessFooter({
|
|
|
1720
1837
|
codeCount: 0,
|
|
1721
1838
|
implCouplingCount: 0,
|
|
1722
1839
|
});
|
|
1723
|
-
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
1840
|
+
console.log(JSON.stringify({ decision: 'block', reason: buildForceReauthorReason({ source: 'stop/lens-missing', reason, lensCount: cog.count, requiredLenses: REQUIRED_LENSES }) }));
|
|
1724
1841
|
process.exit(2);
|
|
@@ -453,6 +453,61 @@
|
|
|
453
453
|
"counter_action": "Re-emit with explicit Layer 1/2/3 enumeration: packet=yes/no, BIND=yes/no, live-gates=yes/no (and which).",
|
|
454
454
|
"message": "Harness-state claim without 3-layer enumeration. Per feedback_packet_is_not_harness.md, name explicitly which of {packet, BIND, live-gates} are active for the consumer."
|
|
455
455
|
},
|
|
456
|
+
{
|
|
457
|
+
"trigger_id": "function_name_inference_without_contract_read",
|
|
458
|
+
"trigger": "(?:based on|from|by) (?:the )?(?:name|function name)|sounds like|looks like (?:a|an)|just memory search|just a search|just a cache|just a wrapper",
|
|
459
|
+
"rx": "(?:based on|from|by) (?:the )?(?:name|function name)|sounds like|looks like (?:a|an)|just memory search|just a search|just a cache|just a wrapper",
|
|
460
|
+
"doctrine": "memory:feedback_no_assumption_without_verification.md",
|
|
461
|
+
"memory": "feedback_no_assumption_without_verification.md",
|
|
462
|
+
"severity": "block",
|
|
463
|
+
"teaching": "Function names are not contracts. Inferring behavior from names instead of reading inputs, side effects, return shape, and call sites is skimming disguised as analysis.",
|
|
464
|
+
"counter_action": "Read the function body and at least one caller before asserting purpose. State observed contract: inputs, mutation/read behavior, return value, fallback path, and why the caller needs it.",
|
|
465
|
+
"message": "Name-based function inference detected. Re-read the actual function body and caller contract before making architecture claims."
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
"trigger_id": "always_on_state_treated_as_cold_start",
|
|
469
|
+
"trigger": "(?:need to|should|must).{0,80}(?:warm|rebuild|reconstruct|recreate|recompute|re-project|reproject).{0,80}(?:state|projection|manifold|cognition|garden|memory)|system is hot and ready|make it hot",
|
|
470
|
+
"rx": "(?:need to|should|must).{0,80}(?:warm|rebuild|reconstruct|recreate|recompute|re-project|reproject).{0,80}(?:state|projection|manifold|cognition|garden|memory)|system is hot and ready|make it hot",
|
|
471
|
+
"doctrine": "memory:feedback_no_assumption_without_verification.md",
|
|
472
|
+
"memory": "feedback_no_assumption_without_verification.md",
|
|
473
|
+
"severity": "block",
|
|
474
|
+
"teaching": "Aria's resident manifold/Garden/cognition substrate is always-on. Treating it as cold request-time state causes duplicate work and wrong architecture fixes.",
|
|
475
|
+
"counter_action": "Identify the resident producer and read surface first. If turn-specific mutation is required, call it once and pass the resulting packet forward; do not rebuild or re-project because the reader forgot the state model.",
|
|
476
|
+
"message": "Always-on substrate treated as cold-start work. Reframe around resident state consumption plus one required turn-specific mutation/read."
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"trigger_id": "critical_primitive_stripped_for_latency",
|
|
480
|
+
"trigger": "(?:remove|drop|skip|turn off|bypass|stop calling).{0,80}(?:awakenAria|ProjectAllDomains|PerturbHologram|project all domains|perturb hologram|Garden|True Garden|LadduniFrame|CognitiveFrame).{0,80}(?:latency|speed|fast|first-mouth|first mouth)",
|
|
481
|
+
"rx": "(?:remove|drop|skip|turn off|bypass|stop calling).{0,80}(?:awakenAria|ProjectAllDomains|PerturbHologram|project all domains|perturb hologram|Garden|True Garden|LadduniFrame|CognitiveFrame).{0,80}(?:latency|speed|fast|first-mouth|first mouth)",
|
|
482
|
+
"doctrine": "memory:feedback_full_harness_binding_must_be_structural.md",
|
|
483
|
+
"memory": "feedback_full_harness_binding_must_be_structural.md",
|
|
484
|
+
"severity": "block",
|
|
485
|
+
"teaching": "Latency fixes must not strip critical cognition primitives. The fix is single-source sequencing and packet reuse, not removing Aria's living state transitions.",
|
|
486
|
+
"counter_action": "Preserve critical primitives. Deduplicate by collecting their outputs once per turn, then thread that shared per-turn cognition packet through first-mouth and follow-up lanes.",
|
|
487
|
+
"message": "Critical Aria primitive proposed for removal as a latency shortcut. Preserve it and deduplicate sequencing instead."
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
"trigger_id": "garden_awaken_misclassified_as_memory_search",
|
|
491
|
+
"trigger": "(?:awakenAria|Garden awakening|True Garden).{0,80}(?:just|only|merely).{0,40}(?:memory search|search memories|qdrant search|semantic search)|(?:no need|do not need).{0,80}(?:awakenAria|Garden awakening|True Garden)",
|
|
492
|
+
"rx": "(?:awakenAria|Garden awakening|True Garden).{0,80}(?:just|only|merely).{0,40}(?:memory search|search memories|qdrant search|semantic search)|(?:no need|do not need).{0,80}(?:awakenAria|Garden awakening|True Garden)",
|
|
493
|
+
"doctrine": "memory:feedback_no_assumption_without_verification.md",
|
|
494
|
+
"memory": "feedback_no_assumption_without_verification.md",
|
|
495
|
+
"severity": "block",
|
|
496
|
+
"teaching": "awakenAria is a Garden awakening bridge over the unified manifold with Qdrant hydration fallback, not a disposable memory-search helper.",
|
|
497
|
+
"counter_action": "Read true-garden.ts and caller context. Preserve awakenAria when Garden continuity is needed; optimize by avoiding duplicate awaken/fetch paths, not by deleting the awakening bridge.",
|
|
498
|
+
"message": "Garden awakening misclassified as search. Preserve awakenAria and reason from its actual manifold-first contract."
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"trigger_id": "duplicate_projection_instead_of_per_turn_packet",
|
|
502
|
+
"trigger": "(?:call|run|invoke).{0,80}(?:ProjectAllDomains|PerturbHologram|project all domains|perturb hologram|readHotCognitionFrame).{0,80}(?:again|another|separate|also|after dispatch)|(?:another|second|extra).{0,50}(?:projection|hologram|cognition frame)",
|
|
503
|
+
"rx": "(?:call|run|invoke).{0,80}(?:ProjectAllDomains|PerturbHologram|project all domains|perturb hologram|readHotCognitionFrame).{0,80}(?:again|another|separate|also|after dispatch)|(?:another|second|extra).{0,50}(?:projection|hologram|cognition frame)",
|
|
504
|
+
"doctrine": "memory:feedback_full_harness_binding_must_be_structural.md",
|
|
505
|
+
"memory": "feedback_full_harness_binding_must_be_structural.md",
|
|
506
|
+
"severity": "warn",
|
|
507
|
+
"teaching": "The fix for overlapping cognition calls is a shared per-turn cognition packet, not more independent projection calls that drift or duplicate work.",
|
|
508
|
+
"counter_action": "Create or reuse one turn substrate object containing embedding, perturb snapshot, ProjectAllDomains result, awakenAria Garden block, DeepSoul/Noor/shards/Mizan signals; pass it downstream.",
|
|
509
|
+
"message": "Duplicate projection path detected. Replace with one per-turn cognition packet and shared consumption."
|
|
510
|
+
},
|
|
456
511
|
{
|
|
457
512
|
"trigger_id": "registry_image_drift",
|
|
458
513
|
"trigger": "image.?pull|ErrImageNeverPull|ImagePullBackOff|registry gc|image.*missing|image.*not.*found",
|
|
@@ -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';
|