@aria_asi/cli 0.2.30 → 0.2.32
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 +115 -20
- package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
- package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/codex.js +551 -11
- package/dist/aria-connector/src/connectors/codex.js.map +1 -1
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts +7 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.js +87 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.js.map +1 -0
- package/dist/aria-connector/src/connectors/must-read.d.ts +4 -0
- package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/must-read.js +115 -0
- package/dist/aria-connector/src/connectors/must-read.js.map +1 -0
- package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +27 -9
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.js +231 -19
- package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
- package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/shell.js +76 -3
- package/dist/aria-connector/src/connectors/shell.js.map +1 -1
- package/dist/assets/hooks/aria-agent-handoff.mjs +23 -0
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +121 -28
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
- package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +35 -0
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +383 -93
- package/dist/assets/hooks/aria-preprompt-consult.mjs +28 -2
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +93 -16
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +33 -1
- package/dist/assets/hooks/aria-stop-gate.mjs +346 -81
- package/dist/assets/hooks/doctrine_trigger_map.json +55 -0
- package/dist/assets/hooks/lib/canonical-lenses.mjs +6 -5
- package/dist/assets/hooks/lib/gate-loop-state.mjs +50 -0
- package/dist/assets/hooks/lib/hook-message-window.mjs +121 -0
- package/dist/assets/hooks/test-tier-lens-labeling.mjs +26 -58
- package/dist/assets/opencode-plugins/harness-gate/index.js +40 -5
- package/dist/assets/opencode-plugins/harness-stop/index.js +133 -10
- package/dist/runtime/auth-middleware.mjs +251 -0
- package/dist/runtime/codex-bridge.mjs +644 -0
- package/dist/runtime/discipline/CLAUDE.md +28 -0
- package/dist/runtime/discipline/doctrine_trigger_map.json +534 -0
- package/dist/runtime/doctrine_trigger_map.json +534 -0
- package/dist/runtime/fleet-engine.mjs +231 -0
- package/dist/runtime/harness-daemon.mjs +460 -0
- package/dist/runtime/manifest.json +1 -1
- package/dist/runtime/metering.mjs +100 -0
- package/dist/runtime/onboarding-engine.mjs +89 -0
- package/dist/runtime/plugin-engine.mjs +196 -0
- package/dist/runtime/sdk/BUNDLED.json +1 -1
- package/dist/runtime/sdk/index.d.ts +12 -0
- package/dist/runtime/sdk/index.js +120 -14
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/service.mjs +1140 -48
- package/dist/runtime/workflow-engine.mjs +322 -0
- package/dist/sdk/BUNDLED.json +1 -1
- package/dist/sdk/index.d.ts +12 -0
- package/dist/sdk/index.js +120 -14
- package/dist/sdk/index.js.map +1 -1
- package/hooks/aria-agent-handoff.mjs +23 -0
- package/hooks/aria-cognition-substrate-binding.mjs +121 -28
- package/hooks/aria-harness-via-sdk.mjs +126 -12
- package/hooks/aria-pre-emit-dryrun.mjs +35 -0
- package/hooks/aria-pre-tool-gate.mjs +383 -93
- package/hooks/aria-preprompt-consult.mjs +28 -2
- package/hooks/aria-preturn-memory-gate.mjs +93 -16
- package/hooks/aria-repo-doctrine-gate.mjs +33 -1
- package/hooks/aria-stop-gate.mjs +346 -81
- package/hooks/doctrine_trigger_map.json +55 -0
- package/hooks/lib/canonical-lenses.mjs +6 -5
- package/hooks/lib/gate-loop-state.mjs +50 -0
- package/hooks/lib/hook-message-window.mjs +121 -0
- package/hooks/test-tier-lens-labeling.mjs +26 -58
- package/opencode-plugins/harness-gate/index.js +40 -5
- package/opencode-plugins/harness-stop/index.js +133 -10
- package/package.json +1 -1
- package/runtime-src/auth-middleware.mjs +251 -0
- package/runtime-src/codex-bridge.mjs +644 -0
- package/runtime-src/fleet-engine.mjs +231 -0
- package/runtime-src/harness-daemon.mjs +460 -0
- package/runtime-src/metering.mjs +100 -0
- package/runtime-src/onboarding-engine.mjs +89 -0
- package/runtime-src/plugin-engine.mjs +196 -0
- package/runtime-src/service.mjs +1140 -48
- package/runtime-src/workflow-engine.mjs +322 -0
- package/scripts/bundle-sdk.mjs +5 -0
- package/src/connectors/claude-code.ts +126 -20
- package/src/connectors/codex.ts +559 -10
- package/src/connectors/doctrine-trigger-map.ts +112 -0
- package/src/connectors/must-read.ts +117 -0
- package/src/connectors/opencode.ts +28 -9
- package/src/connectors/runtime.ts +241 -21
- package/src/connectors/shell.ts +78 -3
- package/dist/cli-0.2.0.tgz +0 -0
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
import { readFileSync, appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
38
38
|
import { dirname } from 'node:path';
|
|
39
39
|
import { homedir } from 'node:os';
|
|
40
|
-
import { ALL_LENS_NAMES } from './lib/canonical-lenses.mjs';
|
|
40
|
+
import { ALL_LENS_NAMES, lensNamesForTier } from './lib/canonical-lenses.mjs';
|
|
41
|
+
import { collectTurnWindowFromMessages } from './lib/hook-message-window.mjs';
|
|
41
42
|
|
|
42
43
|
const HOME = homedir();
|
|
43
44
|
const AUDIT = `${HOME}/.claude/aria-cognition-substrate-binding-audit.jsonl`;
|
|
@@ -71,14 +72,64 @@ const LENS_NAMES = ALL_LENS_NAMES;
|
|
|
71
72
|
const ANCHOR_RX = /\b(axiom|frame|memory|doctrine|packet|language):[a-z0-9_\-./]+/gi;
|
|
72
73
|
|
|
73
74
|
const COGNITION_BLOCK_RX = /<cognition>([\s\S]*?)<\/cognition>/i;
|
|
75
|
+
const APPLIED_COGNITION_BLOCK_RX = /<applied_cognition>([\s\S]*?)<\/applied_cognition>/i;
|
|
74
76
|
const FIRST_PRINCIPLE_RX = /\b(first[_\s-]?principle[s]?|first_principle=)\b/i;
|
|
75
77
|
const HARNESS_PACKET_PATH = `${HOME}/.claude/.aria-harness-last-packet.json`;
|
|
76
78
|
const MEMORY_DIR = `${HOME}/.claude/projects/-home-hamzaibrahim1/memory`;
|
|
77
79
|
const RECENT_VIOLATIONS_PATH = `${HOME}/.claude/.aria-recent-violations.jsonl`;
|
|
78
80
|
const THRASHING_STATE_PATH = `${HOME}/.claude/.aria-thrashing-state.json`;
|
|
79
81
|
const CLEAR_VIOLATIONS_SCRIPT = `${HOME}/.claude/aria-clear-violations.sh`;
|
|
82
|
+
|
|
83
|
+
function buildForceReauthorReason({ source, reason, violations = [] }) {
|
|
84
|
+
return [
|
|
85
|
+
'=== ARIA FORCE_REAUTHOR ===',
|
|
86
|
+
`source: ${source}`,
|
|
87
|
+
`reason: ${reason}`,
|
|
88
|
+
'',
|
|
89
|
+
'This is not a terminal error. It is a forced redo instruction for cognition substrate binding.',
|
|
90
|
+
'Do not emit the blocked text. Re-author the cognition so every lens is tied to loaded substrate.',
|
|
91
|
+
'',
|
|
92
|
+
'TEACHING:',
|
|
93
|
+
'- Cognition without live anchors is theater; anchors are the proof of thought.',
|
|
94
|
+
'- A valid redo cites only substrate that is actually loaded this turn.',
|
|
95
|
+
'- The cognition block must reference first_principle and must not cite inactive language state.',
|
|
96
|
+
violations.length ? `\nVIOLATIONS TO FIX:\n${violations.map((v) => `- ${v}`).join('\n')}` : '',
|
|
97
|
+
'',
|
|
98
|
+
'REQUIRED REDO SHAPE:',
|
|
99
|
+
'1. Re-emit <cognition> with all required lenses.',
|
|
100
|
+
'2. Each lens cites >=1 loaded anchor: axiom:, frame:, memory:, doctrine:, packet:, or active language:.',
|
|
101
|
+
'3. Include first_principle explicitly.',
|
|
102
|
+
'4. Add <applied_cognition> with decision_delta, dominant_domain, binds_to, expected_predicate, and artifact_change.',
|
|
103
|
+
'5. Remove repeated blocked substrings instead of reusing them.',
|
|
104
|
+
'=== END FORCE_REAUTHOR ===',
|
|
105
|
+
].filter(Boolean).join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function validateAppliedCognitionContract(text) {
|
|
109
|
+
const match = String(text || '').match(APPLIED_COGNITION_BLOCK_RX);
|
|
110
|
+
if (!match) {
|
|
111
|
+
return { ok: false, violations: ['missing <applied_cognition> contract'] };
|
|
112
|
+
}
|
|
113
|
+
const body = match[1] || '';
|
|
114
|
+
const required = [
|
|
115
|
+
['decision_delta', /\bdecision[_ -]?delta\s*:/i],
|
|
116
|
+
['dominant_domain', /\bdominant[_ -]?domain\s*:/i],
|
|
117
|
+
['binds_to', /\bbinds[_ -]?to\s*:/i],
|
|
118
|
+
['expected_predicate', /\bexpected[_ -]?predicate\s*:/i],
|
|
119
|
+
['artifact_change', /\bartifact[_ -]?change\s*:/i],
|
|
120
|
+
];
|
|
121
|
+
const violations = [];
|
|
122
|
+
for (const [name, rx] of required) {
|
|
123
|
+
if (!rx.test(body)) violations.push(`missing ${name}`);
|
|
124
|
+
}
|
|
125
|
+
const weakDelta = /decision[_ -]?delta\s*:\s*(?:none|n\/a|no change|unchanged|same)/i.test(body);
|
|
126
|
+
if (weakDelta) violations.push('decision_delta says cognition changed nothing');
|
|
127
|
+
return { ok: violations.length === 0, violations };
|
|
128
|
+
}
|
|
80
129
|
const CARRY_FORWARD_WINDOW_MS = 25 * 60 * 1000;
|
|
81
130
|
const CARRY_FORWARD_MAX_ROWS = 200;
|
|
131
|
+
const SYSTEM_REMINDER_RX = /<system-reminder>[\s\S]*?<\/system-reminder>|<task-notification>[\s\S]*?<\/task-notification>|🔐 Aria Harness|task-notification|PreToolUse:[A-Z][A-Za-z]* hook blocking error|Stop hook blocking error/g;
|
|
132
|
+
const SYSTEM_REMINDER_THRESHOLD = 0.6;
|
|
82
133
|
|
|
83
134
|
function extractLensTexts(cognitionInner) {
|
|
84
135
|
const out = {};
|
|
@@ -130,6 +181,22 @@ function loadCarryForwardViolations() {
|
|
|
130
181
|
return rows;
|
|
131
182
|
}
|
|
132
183
|
|
|
184
|
+
function resolveOwnerTier() {
|
|
185
|
+
try {
|
|
186
|
+
if (!existsSync(HARNESS_PACKET_PATH)) return false;
|
|
187
|
+
const packet = JSON.parse(readFileSync(HARNESS_PACKET_PATH, 'utf8'));
|
|
188
|
+
const sigHamza = packet?.contractGate?.signals?.hamza;
|
|
189
|
+
if (sigHamza === true || sigHamza === 'true') return true;
|
|
190
|
+
const harnessStr = packet?.harness ?? '';
|
|
191
|
+
return /\bhamza:true\b/.test(harnessStr);
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const IS_OWNER = resolveOwnerTier();
|
|
198
|
+
const VISIBLE_LENS_NAMES = lensNamesForTier(IS_OWNER);
|
|
199
|
+
|
|
133
200
|
function findCarryForwardMatch(text, rows) {
|
|
134
201
|
const haystack = String(text || '').toLowerCase();
|
|
135
202
|
for (const row of rows) {
|
|
@@ -325,31 +392,39 @@ try {
|
|
|
325
392
|
}
|
|
326
393
|
|
|
327
394
|
const transcriptPath = payload.transcript_path;
|
|
328
|
-
if (!transcriptPath || !existsSync(transcriptPath)) {
|
|
329
|
-
audit('skip_no_transcript', { transcriptPath });
|
|
330
|
-
process.exit(0);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
395
|
let assistantText = '';
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
396
|
+
const messageWindow = collectTurnWindowFromMessages(payload.messages, {
|
|
397
|
+
systemReminderRx: SYSTEM_REMINDER_RX,
|
|
398
|
+
systemReminderThreshold: SYSTEM_REMINDER_THRESHOLD,
|
|
399
|
+
});
|
|
400
|
+
assistantText = messageWindow.assistantText;
|
|
401
|
+
|
|
402
|
+
if (transcriptPath && existsSync(transcriptPath)) {
|
|
403
|
+
try {
|
|
404
|
+
const transcriptEntries = readFileSync(transcriptPath, 'utf8')
|
|
405
|
+
.split('\n')
|
|
406
|
+
.filter(Boolean)
|
|
407
|
+
.map((line) => {
|
|
408
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
409
|
+
})
|
|
410
|
+
.filter(Boolean);
|
|
411
|
+
const transcriptWindow = collectTurnWindowFromMessages(transcriptEntries, {
|
|
412
|
+
systemReminderRx: SYSTEM_REMINDER_RX,
|
|
413
|
+
systemReminderThreshold: SYSTEM_REMINDER_THRESHOLD,
|
|
414
|
+
});
|
|
415
|
+
const transcriptAssistantText = transcriptWindow.assistantText;
|
|
416
|
+
if (transcriptAssistantText) {
|
|
417
|
+
assistantText = assistantText
|
|
418
|
+
? [assistantText, transcriptAssistantText].filter((text, index, arr) => arr.indexOf(text) === index).join('\n\n')
|
|
419
|
+
: transcriptAssistantText;
|
|
420
|
+
}
|
|
421
|
+
} catch (err) {
|
|
422
|
+
audit('skip_transcript_read_err', { err: String(err).slice(0, 200) });
|
|
350
423
|
}
|
|
351
|
-
}
|
|
352
|
-
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (!assistantText) {
|
|
427
|
+
audit('skip_no_assistant_text', { transcriptPath });
|
|
353
428
|
process.exit(0);
|
|
354
429
|
}
|
|
355
430
|
|
|
@@ -395,7 +470,7 @@ Recovery surfaces:
|
|
|
395
470
|
|
|
396
471
|
Per feedback_block_and_force_with_recovery.md, repetition of a recently-blocked phrase is not advisory drift; it is a hard block until the phrase is removed or the carry-forward state is intentionally cleared.`;
|
|
397
472
|
|
|
398
|
-
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
473
|
+
console.log(JSON.stringify({ decision: 'block', reason: buildForceReauthorReason({ source: 'substrate-binding/carry-forward', reason, violations: [`repeated blocked substring: ${carryForwardMatch.substring}`] }) }));
|
|
399
474
|
process.exit(2);
|
|
400
475
|
}
|
|
401
476
|
|
|
@@ -424,17 +499,35 @@ This non-trivial assistant response (${assistantText.length} chars) contains no
|
|
|
424
499
|
The prior exit-0 pattern ("defer to aria-stop-gate.mjs") was a structural lie: this hook runs before the stop-gate, so exit(0) prevented all downstream enforcement. This is now a hard block.
|
|
425
500
|
|
|
426
501
|
Re-emit with:
|
|
427
|
-
1. A <cognition>...</cognition> block containing all 8
|
|
502
|
+
1. A <cognition>...</cognition> block containing all 8 required visible labels for this surface (${VISIBLE_LENS_NAMES.join(', ')})
|
|
428
503
|
2. Each lens citing >=1 verifiable loaded substrate anchor
|
|
429
504
|
3. The block referencing first_principle from the loaded harness packet
|
|
430
505
|
|
|
431
506
|
No process-level disable path per Hamza directive 2026-04-27.`;
|
|
432
507
|
audit('block_no_cognition_block', { length: assistantText.length });
|
|
433
|
-
console.log(JSON.stringify({ decision: 'block', reason: noCogReason }));
|
|
508
|
+
console.log(JSON.stringify({ decision: 'block', reason: buildForceReauthorReason({ source: 'substrate-binding/no-cognition-block', reason: noCogReason, violations: ['missing <cognition>...</cognition> block'] }) }));
|
|
434
509
|
process.exit(2);
|
|
435
510
|
}
|
|
436
511
|
|
|
437
512
|
const cognitionInner = cogMatch[1];
|
|
513
|
+
const appliedContract = validateAppliedCognitionContract(assistantText);
|
|
514
|
+
if (!appliedContract.ok) {
|
|
515
|
+
const reason = `Aria substrate-binding gate: cognition was emitted without an applied-cognition execution contract.
|
|
516
|
+
|
|
517
|
+
The harness no longer accepts cognition as prose-only compliance. The response must state what cognition changed and what concrete action/output it binds.
|
|
518
|
+
|
|
519
|
+
Required block:
|
|
520
|
+
<applied_cognition>
|
|
521
|
+
decision_delta: <what changed because cognition ran; not "none">
|
|
522
|
+
dominant_domain: <engineering_quality | trust | product | operations | security | ...>
|
|
523
|
+
binds_to: <next tool/action/output claim this cognition constrains>
|
|
524
|
+
expected_predicate: <numeric, boolean, or state-string predicate proving success>
|
|
525
|
+
artifact_change: <how the produced artifact differs because cognition ran>
|
|
526
|
+
</applied_cognition>`;
|
|
527
|
+
audit('block_applied_cognition_missing', { violations: appliedContract.violations, length: assistantText.length });
|
|
528
|
+
console.log(JSON.stringify({ decision: 'block', reason: buildForceReauthorReason({ source: 'substrate-binding/applied-cognition-contract', reason, violations: appliedContract.violations }) }));
|
|
529
|
+
process.exit(2);
|
|
530
|
+
}
|
|
438
531
|
const lensTexts = extractLensTexts(cognitionInner);
|
|
439
532
|
|
|
440
533
|
// Substance check — replaces char-count with "lens body has substantive
|
|
@@ -664,5 +757,5 @@ Anchor grammar (each anchor must resolve to a real loaded substrate item):
|
|
|
664
757
|
|
|
665
758
|
Re-emit cognition with: every lens citing ≥1 verifiable loaded anchor, the block referencing first_principle, and language: citations only for active language tiers. No process-level disable path; gates are unconditional from the gated process per Hamza directive 2026-04-27.`;
|
|
666
759
|
|
|
667
|
-
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
760
|
+
console.log(JSON.stringify({ decision: 'block', reason: buildForceReauthorReason({ source: 'substrate-binding/structural-violations', reason, violations: reasonParts }) }));
|
|
668
761
|
process.exit(2);
|
|
@@ -26,7 +26,10 @@ const LOG_FILE = `${HOME}/.claude/aria-harness-history.log`;
|
|
|
26
26
|
const LAST_URL_CACHE = `${HOME}/.claude/.aria-harness-last-url`;
|
|
27
27
|
const PACKET_CACHE = `${HOME}/.claude/.aria-harness-last-packet.json`;
|
|
28
28
|
const SHARED_PACKET_CACHE = `${HOME}/.aria/.aria-harness-last-packet.json`;
|
|
29
|
+
const MIZAN_RECEIPT_DIR = `${HOME}/.claude/.aria-mizan-receipts`;
|
|
29
30
|
const HEADER_PREFIX = '🔐 Aria Harness';
|
|
31
|
+
const DEFAULT_RUNTIME_URL = process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319';
|
|
32
|
+
const RUNTIME_PACKET_SUFFIX = '/packet';
|
|
30
33
|
|
|
31
34
|
const args = process.argv.slice(2);
|
|
32
35
|
const MODE = args.includes('--mode') ? args[args.indexOf('--mode') + 1] : 'session';
|
|
@@ -133,6 +136,8 @@ function buildUrlList() {
|
|
|
133
136
|
const list = [];
|
|
134
137
|
const cached = loadCachedUrl();
|
|
135
138
|
if (cached) list.push(cached);
|
|
139
|
+
if (process.env.ARIA_RUNTIME_URL) list.push(process.env.ARIA_RUNTIME_URL.replace(/\/+$/, ''));
|
|
140
|
+
list.push(DEFAULT_RUNTIME_URL.replace(/\/+$/, ''));
|
|
136
141
|
if (process.env.ARIA_HIVE_RUNTIME_URL) list.push(process.env.ARIA_HIVE_RUNTIME_URL.replace(/\/+$/, ''));
|
|
137
142
|
if (process.env.ARIA_HARNESS_BASE_URL) list.push(process.env.ARIA_HARNESS_BASE_URL.replace(/\/+$/, ''));
|
|
138
143
|
if (process.env.ARIA_HARNESS_URL) list.push(process.env.ARIA_HARNESS_URL.replace(/\/+$/, ''));
|
|
@@ -155,6 +160,21 @@ function buildUrlList() {
|
|
|
155
160
|
return list.filter((u) => (seen.has(u) ? false : (seen.add(u), true)));
|
|
156
161
|
}
|
|
157
162
|
|
|
163
|
+
function isMountedRuntimeUrl(baseUrl) {
|
|
164
|
+
return /:\/\/(?:127\.0\.0\.1|localhost):4319$/i.test(String(baseUrl || '').replace(/\/+$/, ''));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function normalizeHarnessPacketPayload(payload) {
|
|
168
|
+
let current = payload;
|
|
169
|
+
for (let depth = 0; depth < 3; depth++) {
|
|
170
|
+
if (!current || typeof current !== 'object' || Array.isArray(current)) break;
|
|
171
|
+
if (!('packet' in current) || !current.packet || typeof current.packet !== 'object') break;
|
|
172
|
+
if (!('timestamp' in current) && !('version' in current)) break;
|
|
173
|
+
current = current.packet;
|
|
174
|
+
}
|
|
175
|
+
return current;
|
|
176
|
+
}
|
|
177
|
+
|
|
158
178
|
// SDK loader — dynamic-import the bundled HTTPHarnessClient from the shared
|
|
159
179
|
// ~/.aria/sdk bundle first, then client-local bundles. Module-cached after
|
|
160
180
|
// first load so we don't repeatedly read disk.
|
|
@@ -204,6 +224,7 @@ async function loadSdkClass() {
|
|
|
204
224
|
// before forwarding to avoid blowing the POST body size limit; the most recent
|
|
205
225
|
// messages are the most relevant for history-count purposes.
|
|
206
226
|
let HOOK_EVENT_MESSAGES = undefined;
|
|
227
|
+
let HOOK_EVENT = null;
|
|
207
228
|
try {
|
|
208
229
|
// Claude Code hooks receive the event JSON on stdin. We read it synchronously
|
|
209
230
|
// only if data is already available (i.e. piped); we don't block waiting for
|
|
@@ -211,6 +232,7 @@ try {
|
|
|
211
232
|
// a terminal (e.g. manual invocation for testing).
|
|
212
233
|
const stdinBuf = readFileSync('/dev/stdin', { flag: 'r' });
|
|
213
234
|
const hookEvent = JSON.parse(stdinBuf.toString('utf8'));
|
|
235
|
+
HOOK_EVENT = hookEvent;
|
|
214
236
|
if (Array.isArray(hookEvent?.messages) && hookEvent.messages.length > 0) {
|
|
215
237
|
// Cap to last 50 messages. The server-side handler slices further if needed.
|
|
216
238
|
HOOK_EVENT_MESSAGES = hookEvent.messages.slice(-50);
|
|
@@ -219,6 +241,81 @@ try {
|
|
|
219
241
|
// stdin not available / not JSON / no messages field — HOOK_EVENT_MESSAGES stays undefined.
|
|
220
242
|
}
|
|
221
243
|
|
|
244
|
+
function sanitizeSessionId(sessionId) {
|
|
245
|
+
return String(sessionId || 'claude-code').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function lastUserMessageFromEvent() {
|
|
249
|
+
const messages = Array.isArray(HOOK_EVENT?.messages) ? HOOK_EVENT.messages : [];
|
|
250
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
251
|
+
const message = messages[index];
|
|
252
|
+
const role = message?.role || message?.message?.role || message?.type;
|
|
253
|
+
if (role !== 'user') continue;
|
|
254
|
+
const content = message?.content ?? message?.message?.content;
|
|
255
|
+
if (typeof content === 'string' && content.trim()) return content.trim();
|
|
256
|
+
if (Array.isArray(content)) {
|
|
257
|
+
const text = content
|
|
258
|
+
.filter((entry) => entry?.type === 'text' && typeof entry?.text === 'string')
|
|
259
|
+
.map((entry) => entry.text.trim())
|
|
260
|
+
.filter(Boolean)
|
|
261
|
+
.join('\n');
|
|
262
|
+
if (text) return text;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return isTurn ? 'Claude turn refresh via harness hook' : 'Claude session start via harness hook';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function receiptPathForSession(sessionId) {
|
|
269
|
+
return `${MIZAN_RECEIPT_DIR}/${sanitizeSessionId(sessionId)}.json`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function persistMizanReceipt(sessionId, payload) {
|
|
273
|
+
try {
|
|
274
|
+
mkdirSync(MIZAN_RECEIPT_DIR, { recursive: true });
|
|
275
|
+
writeFileSync(receiptPathForSession(sessionId), JSON.stringify(payload, null, 2) + '\n');
|
|
276
|
+
} catch {}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function runMizanPre(apiKey, packet, sourceLabel) {
|
|
280
|
+
if (!isTurn) return null;
|
|
281
|
+
const sessionId = HOOK_EVENT?.session_id || HOOK_EVENT?.sessionId || 'claude-code';
|
|
282
|
+
const response = await fetch(`${DEFAULT_RUNTIME_URL.replace(/\/+$/, '')}/mizan/pre`, {
|
|
283
|
+
method: 'POST',
|
|
284
|
+
headers: {
|
|
285
|
+
Authorization: `Bearer ${apiKey}`,
|
|
286
|
+
'Content-Type': 'application/json',
|
|
287
|
+
},
|
|
288
|
+
body: JSON.stringify({
|
|
289
|
+
sessionId,
|
|
290
|
+
packet,
|
|
291
|
+
context: {
|
|
292
|
+
sessionId,
|
|
293
|
+
message: lastUserMessageFromEvent().slice(0, 4000),
|
|
294
|
+
intendedAction: 'Establish canonical pre-turn cognition receipt for this Claude turn before any tool or output work.',
|
|
295
|
+
rationale: `Claude turn-start harness sync via ${sourceLabel} must mint a canonical Mizan pre receipt for downstream tool and output enforcement.`,
|
|
296
|
+
platform: 'claude-code',
|
|
297
|
+
stage: 'hook-turn-pre',
|
|
298
|
+
},
|
|
299
|
+
}),
|
|
300
|
+
});
|
|
301
|
+
const payload = await response.json().catch(() => ({}));
|
|
302
|
+
if (!response.ok) {
|
|
303
|
+
throw new Error(payload?.error || `mizan/pre failed (${response.status})`);
|
|
304
|
+
}
|
|
305
|
+
if (payload?.receipt) {
|
|
306
|
+
persistMizanReceipt(sessionId, {
|
|
307
|
+
updatedAt: new Date().toISOString(),
|
|
308
|
+
source: sourceLabel,
|
|
309
|
+
sessionId,
|
|
310
|
+
receipt: payload.receipt,
|
|
311
|
+
result: payload.result || null,
|
|
312
|
+
contract: payload.contract || null,
|
|
313
|
+
summary: payload.summary || null,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return payload;
|
|
317
|
+
}
|
|
318
|
+
|
|
222
319
|
async function tryViaSdk(baseUrl, apiKey) {
|
|
223
320
|
// Canonical path: HTTPHarnessClient.getHarnessPacket(). The SDK POSTs to
|
|
224
321
|
// /api/harness/codex with the right shape and returns { packet, timestamp,
|
|
@@ -228,16 +325,19 @@ async function tryViaSdk(baseUrl, apiKey) {
|
|
|
228
325
|
// SDK's `body.packet ?? body` passes the body through unchanged).
|
|
229
326
|
const Cls = await loadSdkClass();
|
|
230
327
|
if (Cls) {
|
|
328
|
+
const packetUrl = isMountedRuntimeUrl(baseUrl)
|
|
329
|
+
? `${baseUrl}${RUNTIME_PACKET_SUFFIX}`
|
|
330
|
+
: `${baseUrl}/api/harness/codex`;
|
|
231
331
|
const client = new Cls({
|
|
232
332
|
baseUrl,
|
|
233
333
|
apiKey,
|
|
234
|
-
harnessPacketUrl:
|
|
334
|
+
harnessPacketUrl: packetUrl,
|
|
235
335
|
});
|
|
236
336
|
// Pass a bodyOverride that does NOT include isHamza:false. The server
|
|
237
337
|
// identifies owner tier from the Bearer token (isMasterTokenRequest).
|
|
238
338
|
// Hardcoding isHamza:false in the body would override that server-side
|
|
239
339
|
// signal and produce hamza:false in the packet even for master-token callers.
|
|
240
|
-
|
|
340
|
+
const bodyOverride = {
|
|
241
341
|
message: isTurn ? 'Claude turn refresh — harness via SDK' : 'Claude session start — harness via SDK',
|
|
242
342
|
stage: isTurn ? 'checkpoint' : 'preflight',
|
|
243
343
|
actor: 'claude-code',
|
|
@@ -246,15 +346,19 @@ async function tryViaSdk(baseUrl, apiKey) {
|
|
|
246
346
|
// Bonus #74: forward conversation history so codex handler can report
|
|
247
347
|
// a non-zero conversation_history_count in the packet.
|
|
248
348
|
...(HOOK_EVENT_MESSAGES ? { messages: HOOK_EVENT_MESSAGES } : {}),
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
349
|
+
};
|
|
350
|
+
const wrapped = await client.getHarnessPacket(bodyOverride);
|
|
351
|
+
const json = normalizeHarnessPacketPayload(wrapped.packet);
|
|
352
|
+
if (json && json.ok === false) throw new Error(`ok=false: ${json.error || 'unknown'}`);
|
|
353
|
+
await runMizanPre(apiKey, wrapped.packet, `${baseUrl}${RUNTIME_PACKET_SUFFIX}`);
|
|
354
|
+
return { json, raw: JSON.stringify(json) };
|
|
355
|
+
}
|
|
255
356
|
|
|
357
|
+
const packetUrl = isMountedRuntimeUrl(baseUrl)
|
|
358
|
+
? `${baseUrl}${RUNTIME_PACKET_SUFFIX}`
|
|
359
|
+
: `${baseUrl}/api/harness/codex`;
|
|
256
360
|
// SDK absent (dev environment) — direct fetch with identical wire shape.
|
|
257
|
-
const resp = await fetch(
|
|
361
|
+
const resp = await fetch(packetUrl, {
|
|
258
362
|
method: 'POST',
|
|
259
363
|
headers: {
|
|
260
364
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -272,8 +376,10 @@ async function tryViaSdk(baseUrl, apiKey) {
|
|
|
272
376
|
}),
|
|
273
377
|
});
|
|
274
378
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
275
|
-
const
|
|
379
|
+
const responseBody = await resp.json();
|
|
380
|
+
const json = normalizeHarnessPacketPayload(responseBody.packet ?? responseBody);
|
|
276
381
|
if (json && json.ok === false) throw new Error(`ok=false: ${json.error || 'unknown'}`);
|
|
382
|
+
await runMizanPre(apiKey, responseBody.packet ?? responseBody, packetUrl);
|
|
277
383
|
return { json, raw: JSON.stringify(json) };
|
|
278
384
|
}
|
|
279
385
|
|
|
@@ -284,6 +390,9 @@ function renderPacket(json, source, ageNote = '') {
|
|
|
284
390
|
const con = json.contractGate || {};
|
|
285
391
|
const mc = json.missingCritical || [];
|
|
286
392
|
const loaded = json.loadedByClass || {};
|
|
393
|
+
const offlineBundle = json.runtimeOfflineBundle && typeof json.runtimeOfflineBundle === 'object'
|
|
394
|
+
? json.runtimeOfflineBundle
|
|
395
|
+
: null;
|
|
287
396
|
|
|
288
397
|
let header = `${HEADER_PREFIX} (${MODE}${ageNote}, sdk=harness-http-client) hash=${phash.slice(0, 16)} via=${source}\n`;
|
|
289
398
|
header += ` preStateGate: passed=${pre.passed} score=${(pre.score || 0).toFixed(2)}`;
|
|
@@ -292,6 +401,9 @@ function renderPacket(json, source, ageNote = '') {
|
|
|
292
401
|
if (mc.length) header += `\n missingCritical: ${JSON.stringify(mc.slice(0, 5))}`;
|
|
293
402
|
const loadedSummary = Object.entries(loaded).sort().map(([k, v]) => `${k}=${v}`).join(' ');
|
|
294
403
|
if (loadedSummary) header += `\n loadedByClass: ${loadedSummary}`;
|
|
404
|
+
if (offlineBundle) {
|
|
405
|
+
header += `\n offlineBundle: phase=${offlineBundle.phase || 'unknown'} age=${offlineBundle.ageSeconds ?? 'unknown'}s source=${offlineBundle.source || 'runtime-cache'}`;
|
|
406
|
+
}
|
|
295
407
|
|
|
296
408
|
let ctx = header;
|
|
297
409
|
if (harness) {
|
|
@@ -344,8 +456,9 @@ async function main() {
|
|
|
344
456
|
const stat = fs.statSync(PACKET_CACHE);
|
|
345
457
|
const ageSec = (Date.now() - stat.mtimeMs) / 1000;
|
|
346
458
|
if (ageSec < PACKET_CACHE_TTL_SEC) {
|
|
347
|
-
|
|
348
|
-
|
|
459
|
+
const cached = JSON.parse(fs.readFileSync(PACKET_CACHE, 'utf8'));
|
|
460
|
+
await runMizanPre(apiKey, cached, '(cache)');
|
|
461
|
+
return renderPacket(cached, '(cache)', ` cached ${Math.round(ageSec)}s`);
|
|
349
462
|
}
|
|
350
463
|
} catch {}
|
|
351
464
|
}
|
|
@@ -396,6 +509,7 @@ async function main() {
|
|
|
396
509
|
const ageSec = (Date.now() - stat.mtimeMs) / 1000;
|
|
397
510
|
if (ageSec >= 0) {
|
|
398
511
|
const cached = JSON.parse(fs.readFileSync(PACKET_CACHE, 'utf8'));
|
|
512
|
+
await runMizanPre(apiKey, cached, '(stale-cache)');
|
|
399
513
|
try {
|
|
400
514
|
appendFileSync(LOG_FILE, `${new Date().toISOString()} stale mode=${MODE} cache_age=${Math.round(ageSec)}s last_err="${lastErr.replace(/"/g, "'")}" sdk=harness-http-client\n`);
|
|
401
515
|
} catch {}
|
|
@@ -53,7 +53,9 @@ const REQUIRED_LENSES = 8;
|
|
|
53
53
|
const SUBSTANCE_MIN_AFTER_ANCHOR_STRIP = 40;
|
|
54
54
|
const ANCHOR_RX = /\b(axiom|frame|memory|doctrine|packet|language):[a-z0-9_\-./]+/gi;
|
|
55
55
|
const COGNITION_BLOCK_RX = /<cognition>([\s\S]*?)<\/cognition>/i;
|
|
56
|
+
const APPLIED_COGNITION_BLOCK_RX = /<applied_cognition>([\s\S]*?)<\/applied_cognition>/i;
|
|
56
57
|
const FIRST_PRINCIPLE_RX = /\b(first[_\s-]?principle[s]?|first_principle=)\b/i;
|
|
58
|
+
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;
|
|
57
59
|
|
|
58
60
|
// Drift trigger patterns (mirror aria-stop-gate.mjs canonical list).
|
|
59
61
|
// Keep this conservative — false negatives are tolerable (real gate will
|
|
@@ -138,6 +140,27 @@ function verifyAnchor(anchor, substrate) {
|
|
|
138
140
|
return { ok: true, reason: '' };
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
function validateAppliedCognitionContract(text) {
|
|
144
|
+
const match = String(text || '').match(APPLIED_COGNITION_BLOCK_RX);
|
|
145
|
+
if (!match) return { ok: false, violations: ['missing <applied_cognition> contract'] };
|
|
146
|
+
const body = match[1] || '';
|
|
147
|
+
const required = [
|
|
148
|
+
['decision_delta', /\bdecision[_ -]?delta\s*:/i],
|
|
149
|
+
['dominant_domain', /\bdominant[_ -]?domain\s*:/i],
|
|
150
|
+
['binds_to', /\bbinds[_ -]?to\s*:/i],
|
|
151
|
+
['expected_predicate', /\bexpected[_ -]?predicate\s*:/i],
|
|
152
|
+
['artifact_change', /\bartifact[_ -]?change\s*:/i],
|
|
153
|
+
];
|
|
154
|
+
const violations = [];
|
|
155
|
+
for (const [name, rx] of required) {
|
|
156
|
+
if (!rx.test(body)) violations.push(`missing ${name}`);
|
|
157
|
+
}
|
|
158
|
+
if (/decision[_ -]?delta\s*:\s*(?:none|n\/a|no change|unchanged|same)/i.test(body)) {
|
|
159
|
+
violations.push('decision_delta says cognition changed nothing');
|
|
160
|
+
}
|
|
161
|
+
return { ok: violations.length === 0, violations };
|
|
162
|
+
}
|
|
163
|
+
|
|
141
164
|
// ── Read draft from stdin ─────────────────────────────────────────────
|
|
142
165
|
let stdin = '';
|
|
143
166
|
try {
|
|
@@ -165,6 +188,7 @@ if (draft.length < 10) {
|
|
|
165
188
|
|
|
166
189
|
const substrate = loadSubstrate();
|
|
167
190
|
const failures = [];
|
|
191
|
+
const requiresAppliedCognition = draft.length >= 300 || DECISION_SIGNAL_RX.test(draft) || ['deploy', 'edit', 'bash', 'text'].includes(actionType);
|
|
168
192
|
|
|
169
193
|
// ── Cognition block presence + lens count + substance ─────────────────
|
|
170
194
|
const cogMatch = draft.match(COGNITION_BLOCK_RX);
|
|
@@ -225,6 +249,17 @@ if (cogMatch) {
|
|
|
225
249
|
}
|
|
226
250
|
}
|
|
227
251
|
|
|
252
|
+
if (requiresAppliedCognition) {
|
|
253
|
+
const applied = validateAppliedCognitionContract(draft);
|
|
254
|
+
if (!applied.ok) {
|
|
255
|
+
failures.push({
|
|
256
|
+
kind: 'applied_cognition_contract_invalid',
|
|
257
|
+
violations: applied.violations,
|
|
258
|
+
message: 'Non-trivial drafts must bind cognition to the actual action/output.',
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
228
263
|
// ── Drift triggers ────────────────────────────────────────────────────
|
|
229
264
|
for (const trigger of DRIFT_TRIGGERS) {
|
|
230
265
|
const match = draft.match(trigger.rx);
|