@aria_asi/cli 0.2.35 → 0.2.37
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/CLIENT-ONBOARDING.md +4 -2
- package/bin/aria.js +11 -7
- package/dist/aria-connector/src/auth.d.ts +14 -0
- package/dist/aria-connector/src/auth.d.ts.map +1 -1
- package/dist/aria-connector/src/auth.js +103 -1
- package/dist/aria-connector/src/auth.js.map +1 -1
- package/dist/aria-connector/src/chat.d.ts.map +1 -1
- package/dist/aria-connector/src/chat.js +13 -8
- package/dist/aria-connector/src/chat.js.map +1 -1
- package/dist/aria-connector/src/config.d.ts +6 -1
- package/dist/aria-connector/src/config.d.ts.map +1 -1
- package/dist/aria-connector/src/config.js.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.js +50 -6
- 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 +312 -10
- package/dist/aria-connector/src/connectors/codex.js.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +35 -11
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/aria-connector/src/connectors/repo-guard.d.ts +10 -0
- package/dist/aria-connector/src/connectors/repo-guard.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/repo-guard.js +110 -164
- package/dist/aria-connector/src/connectors/repo-guard.js.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.js +17 -7
- 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 +12 -8
- package/dist/aria-connector/src/connectors/shell.js.map +1 -1
- package/dist/aria-connector/src/harness-client.d.ts +3 -1
- package/dist/aria-connector/src/harness-client.d.ts.map +1 -1
- package/dist/aria-connector/src/harness-client.js +7 -20
- package/dist/aria-connector/src/harness-client.js.map +1 -1
- package/dist/aria-connector/src/model-context.d.ts.map +1 -1
- package/dist/aria-connector/src/model-context.js +5 -0
- package/dist/aria-connector/src/model-context.js.map +1 -1
- package/dist/aria-connector/src/providers/types.d.ts +1 -1
- package/dist/aria-connector/src/providers/types.d.ts.map +1 -1
- package/dist/aria-connector/src/providers/xai.d.ts +3 -0
- package/dist/aria-connector/src/providers/xai.d.ts.map +1 -0
- package/dist/aria-connector/src/providers/xai.js +40 -0
- package/dist/aria-connector/src/providers/xai.js.map +1 -0
- package/dist/aria-connector/src/setup-wizard.js +1 -0
- package/dist/aria-connector/src/setup-wizard.js.map +1 -1
- package/dist/aria-connector/src/types.d.ts +2 -0
- package/dist/aria-connector/src/types.d.ts.map +1 -1
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +51 -9
- package/dist/assets/hooks/aria-first-class-coach.mjs +129 -0
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +33 -6
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +33 -8
- package/dist/assets/hooks/aria-preprompt-consult.mjs +5 -6
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +5 -0
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +15 -0
- package/dist/assets/hooks/aria-stop-gate.mjs +125 -17
- package/dist/assets/hooks/doctrine_trigger_map.json +11 -0
- package/dist/assets/hooks/lib/emergency-gateoff-impl.mjs +39 -0
- package/dist/assets/hooks/lib/emergency-gateoff.mjs +6 -0
- package/dist/assets/hooks/lib/first-class-coach.mjs +755 -0
- package/dist/assets/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
- package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -14
- package/dist/assets/opencode-plugins/harness-context/auth-token.mjs +126 -0
- package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +62 -22
- package/dist/assets/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
- package/dist/assets/opencode-plugins/harness-gate/index.js +87 -27
- package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
- package/dist/assets/opencode-plugins/harness-outcome/index.js +29 -24
- package/dist/assets/opencode-plugins/harness-stop/index.js +229 -68
- package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
- package/dist/runtime/auth-token.mjs +121 -0
- package/dist/runtime/coach-kernel.mjs +371 -0
- package/dist/runtime/codex-bridge.mjs +440 -69
- package/dist/runtime/discipline/doctrine_trigger_map.json +11 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +18 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +18 -0
- package/dist/runtime/doctrine_trigger_map.json +11 -0
- package/dist/runtime/hooks/aria-cognition-substrate-binding.mjs +51 -9
- package/dist/runtime/hooks/aria-first-class-coach.mjs +129 -0
- package/dist/runtime/hooks/aria-harness-via-sdk.mjs +33 -6
- package/dist/runtime/hooks/aria-pre-tool-gate.mjs +33 -8
- package/dist/runtime/hooks/aria-preprompt-consult.mjs +5 -6
- package/dist/runtime/hooks/aria-preturn-memory-gate.mjs +5 -0
- package/dist/runtime/hooks/aria-repo-doctrine-gate.mjs +15 -0
- package/dist/runtime/hooks/aria-stop-gate.mjs +125 -17
- package/dist/runtime/hooks/doctrine_trigger_map.json +11 -0
- package/dist/runtime/hooks/lib/emergency-gateoff-impl.mjs +39 -0
- package/dist/runtime/hooks/lib/emergency-gateoff.mjs +6 -0
- package/dist/runtime/hooks/lib/first-class-coach.mjs +755 -0
- package/dist/runtime/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
- package/dist/runtime/hooks/lib/skill-autoload-gate.mjs +1 -14
- package/dist/runtime/local-phase.mjs +8 -0
- package/dist/runtime/manifest.json +2 -2
- package/dist/runtime/provider-proxy.mjs +136 -33
- package/dist/runtime/sdk/BUNDLED.json +2 -2
- package/dist/runtime/sdk/auth.d.ts +17 -0
- package/dist/runtime/sdk/auth.js +158 -0
- package/dist/runtime/sdk/auth.js.map +1 -0
- package/dist/runtime/sdk/index.d.ts +8 -1
- package/dist/runtime/sdk/index.js +15 -1
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/service.mjs +1711 -74
- package/dist/runtime/task-project-ledger.mjs +290 -0
- package/dist/sdk/BUNDLED.json +2 -2
- package/dist/sdk/auth.d.ts +17 -0
- package/dist/sdk/auth.js +158 -0
- package/dist/sdk/auth.js.map +1 -0
- package/dist/sdk/index.d.ts +8 -1
- package/dist/sdk/index.js +15 -1
- package/dist/sdk/index.js.map +1 -1
- package/hooks/aria-cognition-substrate-binding.mjs +51 -9
- package/hooks/aria-first-class-coach.mjs +129 -0
- package/hooks/aria-harness-via-sdk.mjs +33 -6
- package/hooks/aria-pre-tool-gate.mjs +33 -8
- package/hooks/aria-preprompt-consult.mjs +5 -6
- package/hooks/aria-preturn-memory-gate.mjs +5 -0
- package/hooks/aria-repo-doctrine-gate.mjs +15 -0
- package/hooks/aria-stop-gate.mjs +125 -17
- package/hooks/doctrine_trigger_map.json +11 -0
- package/hooks/lib/emergency-gateoff-impl.mjs +39 -0
- package/hooks/lib/emergency-gateoff.mjs +6 -0
- package/hooks/lib/first-class-coach.mjs +755 -0
- package/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
- package/hooks/lib/skill-autoload-gate.mjs +1 -14
- package/opencode-plugins/harness-context/auth-token.mjs +126 -0
- package/opencode-plugins/harness-context/inject-context.mjs +62 -22
- package/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
- package/opencode-plugins/harness-gate/index.js +87 -27
- package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
- package/opencode-plugins/harness-outcome/index.js +29 -24
- package/opencode-plugins/harness-stop/index.js +229 -68
- package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
- package/package.json +8 -2
- package/runtime-src/auth-token.mjs +121 -0
- package/runtime-src/coach-kernel.mjs +371 -0
- package/runtime-src/codex-bridge.mjs +440 -69
- package/runtime-src/local-phase.mjs +8 -0
- package/runtime-src/provider-proxy.mjs +136 -33
- package/runtime-src/service.mjs +1711 -74
- package/scripts/bundle-sdk.mjs +8 -0
- package/scripts/check-client-compatibility.mjs +422 -0
- package/scripts/check-coach-kernel.mjs +204 -0
- package/scripts/check-managed-runtime-ledger.mjs +107 -0
- package/scripts/check-opencode-config-contract.mjs +78 -0
- package/scripts/check-quality-ledger.mjs +121 -0
- package/scripts/self-test-harness-gates.mjs +179 -11
- package/scripts/self-test-repo-guard.mjs +38 -0
- package/scripts/validate-skill-prompts.mjs +14 -1
- package/skills/aria-cognition/aria-essence/SKILL.md +18 -0
- package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
- package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
- package/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
- package/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
- package/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
- package/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
- package/skills/aria-cognition/mizan/SKILL.md +18 -0
- package/skills/aria-cognition/nadia/SKILL.md +18 -0
- package/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
- package/skills/aria-cognition/predictor/SKILL.md +18 -0
- package/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
- package/skills/aria-cognition/soul-domains/SKILL.md +18 -0
- package/src/auth.ts +136 -1
- package/src/chat.ts +13 -8
- package/src/config.ts +6 -1
- package/src/connectors/claude-code.ts +62 -18
- package/src/connectors/codex.ts +310 -10
- package/src/connectors/opencode.ts +35 -12
- package/src/connectors/repo-guard.ts +117 -172
- package/src/connectors/runtime.ts +19 -7
- package/src/connectors/shell.ts +12 -8
- package/src/harness-client.ts +8 -22
- package/src/model-context.ts +6 -0
- package/src/providers/types.ts +1 -1
- package/src/providers/xai.ts +55 -0
- package/src/setup-wizard.ts +1 -0
- package/src/types.ts +2 -0
|
@@ -34,9 +34,10 @@
|
|
|
34
34
|
// 2026-04-27 gates are unconditional from the gated process. If the gate
|
|
35
35
|
// misfires on legitimate cognition, fix the gate.
|
|
36
36
|
|
|
37
|
-
import { readFileSync, appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
37
|
+
import { readFileSync, appendFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
38
38
|
import { dirname } from 'node:path';
|
|
39
39
|
import { homedir } from 'node:os';
|
|
40
|
+
import { createHash } from 'node:crypto';
|
|
40
41
|
import { ALL_LENS_NAMES, lensNamesForTier } from './lib/canonical-lenses.mjs';
|
|
41
42
|
import { collectTurnWindowFromMessages } from './lib/hook-message-window.mjs';
|
|
42
43
|
|
|
@@ -79,6 +80,7 @@ const MEMORY_DIR = `${HOME}/.claude/projects/-home-hamzaibrahim1/memory`;
|
|
|
79
80
|
const RECENT_VIOLATIONS_PATH = `${HOME}/.claude/.aria-recent-violations.jsonl`;
|
|
80
81
|
const THRASHING_STATE_PATH = `${HOME}/.claude/.aria-thrashing-state.json`;
|
|
81
82
|
const CLEAR_VIOLATIONS_SCRIPT = `${HOME}/.claude/aria-clear-violations.sh`;
|
|
83
|
+
const CURRENT_RECOVERY_PATH = `${HOME}/.aria/governance-recovery-current.json`;
|
|
82
84
|
|
|
83
85
|
function buildForceReauthorReason({ source, reason, violations = [] }) {
|
|
84
86
|
return [
|
|
@@ -95,6 +97,11 @@ function buildForceReauthorReason({ source, reason, violations = [] }) {
|
|
|
95
97
|
'- The cognition block must reference first_principle and must not cite inactive language state.',
|
|
96
98
|
violations.length ? `\nVIOLATIONS TO FIX:\n${violations.map((v) => `- ${v}`).join('\n')}` : '',
|
|
97
99
|
'',
|
|
100
|
+
'Recovery contract:',
|
|
101
|
+
'1. Do not retry the same blocked text unchanged.',
|
|
102
|
+
'2. Apply the teaching above to the next draft before re-emitting.',
|
|
103
|
+
'3. Re-test by submitting the revised cognition through this same gate.',
|
|
104
|
+
'',
|
|
98
105
|
'REQUIRED REDO SHAPE:',
|
|
99
106
|
'1. Re-emit <cognition> with all required lenses.',
|
|
100
107
|
'2. Each lens cites >=1 loaded anchor: axiom:, frame:, memory:, doctrine:, packet:, or active language:.',
|
|
@@ -105,6 +112,45 @@ function buildForceReauthorReason({ source, reason, violations = [] }) {
|
|
|
105
112
|
].filter(Boolean).join('\n');
|
|
106
113
|
}
|
|
107
114
|
|
|
115
|
+
function recoveryFingerprint(reason, source = 'substrate-binding') {
|
|
116
|
+
return createHash('sha256')
|
|
117
|
+
.update([source, String(reason || '').replace(/\s+/g, ' ').trim().slice(0, 1200)].join('\0'))
|
|
118
|
+
.digest('hex');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function emitRecoverableSubstrateBinding(reason, { source = 'substrate-binding' } = {}) {
|
|
122
|
+
const fingerprint = recoveryFingerprint(reason, source);
|
|
123
|
+
const recovery = {
|
|
124
|
+
schema: 'aria.governance_recovery_current.v1',
|
|
125
|
+
updatedAt: new Date().toISOString(),
|
|
126
|
+
deliveryRule: 'This file is injected into the next system prompt. Execute recoveryLoop.nextStep before any completion claim.',
|
|
127
|
+
ok: true,
|
|
128
|
+
decision: 'warn',
|
|
129
|
+
source,
|
|
130
|
+
governanceMode: 'recovery-required',
|
|
131
|
+
recoveryLoop: {
|
|
132
|
+
fingerprint,
|
|
133
|
+
allowedRecoveryAttempts: 1,
|
|
134
|
+
priorRecoveryAttempts: 0,
|
|
135
|
+
remainingRecoveryAttempts: 1,
|
|
136
|
+
nextStep: 'Re-author the cognition/output with loaded substrate anchors, applied_cognition, and first_principle, then re-run the same substrate-binding gate.',
|
|
137
|
+
architectFallback: 'If Aria consult is unavailable or this same fingerprint repeats, use the strongest available client LLM under the architect harness, produce and execute the recovery plan, then verify.',
|
|
138
|
+
},
|
|
139
|
+
recoveryContract: {
|
|
140
|
+
retry: 'One recovery attempt is allowed for this exact substrate-binding fingerprint; repeat failure enters architect execution mode.',
|
|
141
|
+
architectMode: 'On repeat, load architecture-decision plus substrate-binding/output-discipline skills, define the smallest executable plan, execute the first recovery action, and verify before output.',
|
|
142
|
+
fallbackWhenAriaUnavailable: 'Do not stop or ask the owner to fix it. Bind the strongest available client LLM to the architect harness, give it this JSON contract and failed evidence, execute its recovery plan, then verify.',
|
|
143
|
+
repairRecoveryCycle: ['load or cite only real substrate anchors', 'include first_principle', 'include applied_cognition', 'remove repeated blocked substrings'],
|
|
144
|
+
retest: 'Submit the revised cognition/output through the same substrate-binding gate.',
|
|
145
|
+
blockedReason: reason,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
mkdirSync(dirname(CURRENT_RECOVERY_PATH), { recursive: true, mode: 0o700 });
|
|
149
|
+
writeFileSync(CURRENT_RECOVERY_PATH, `${JSON.stringify(recovery, null, 2)}\n`, { mode: 0o600 });
|
|
150
|
+
console.log(JSON.stringify({ decision: 'allow', recoveryRequired: recovery }));
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
|
|
108
154
|
function validateAppliedCognitionContract(text) {
|
|
109
155
|
const match = String(text || '').match(APPLIED_COGNITION_BLOCK_RX);
|
|
110
156
|
if (!match) {
|
|
@@ -470,8 +516,7 @@ Recovery surfaces:
|
|
|
470
516
|
|
|
471
517
|
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.`;
|
|
472
518
|
|
|
473
|
-
|
|
474
|
-
process.exit(2);
|
|
519
|
+
emitRecoverableSubstrateBinding(buildForceReauthorReason({ source: 'substrate-binding/carry-forward', reason, violations: [`repeated blocked substring: ${carryForwardMatch.substring}`] }), { source: 'substrate-binding/carry-forward' });
|
|
475
520
|
}
|
|
476
521
|
|
|
477
522
|
const cogMatch = assistantText.match(COGNITION_BLOCK_RX);
|
|
@@ -505,8 +550,7 @@ Re-emit with:
|
|
|
505
550
|
|
|
506
551
|
No process-level disable path per Hamza directive 2026-04-27.`;
|
|
507
552
|
audit('block_no_cognition_block', { length: assistantText.length });
|
|
508
|
-
|
|
509
|
-
process.exit(2);
|
|
553
|
+
emitRecoverableSubstrateBinding(buildForceReauthorReason({ source: 'substrate-binding/no-cognition-block', reason: noCogReason, violations: ['missing <cognition>...</cognition> block'] }), { source: 'substrate-binding/no-cognition-block' });
|
|
510
554
|
}
|
|
511
555
|
|
|
512
556
|
const cognitionInner = cogMatch[1];
|
|
@@ -525,8 +569,7 @@ expected_predicate: <numeric, boolean, or state-string predicate proving success
|
|
|
525
569
|
artifact_change: <how the produced artifact differs because cognition ran>
|
|
526
570
|
</applied_cognition>`;
|
|
527
571
|
audit('block_applied_cognition_missing', { violations: appliedContract.violations, length: assistantText.length });
|
|
528
|
-
|
|
529
|
-
process.exit(2);
|
|
572
|
+
emitRecoverableSubstrateBinding(buildForceReauthorReason({ source: 'substrate-binding/applied-cognition-contract', reason, violations: appliedContract.violations }), { source: 'substrate-binding/applied-cognition-contract' });
|
|
530
573
|
}
|
|
531
574
|
const lensTexts = extractLensTexts(cognitionInner);
|
|
532
575
|
|
|
@@ -757,5 +800,4 @@ Anchor grammar (each anchor must resolve to a real loaded substrate item):
|
|
|
757
800
|
|
|
758
801
|
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.`;
|
|
759
802
|
|
|
760
|
-
|
|
761
|
-
process.exit(2);
|
|
803
|
+
emitRecoverableSubstrateBinding(buildForceReauthorReason({ source: 'substrate-binding/structural-violations', reason, violations: reasonParts }), { source: 'substrate-binding/structural-violations' });
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import {
|
|
4
|
+
FIRST_CLASS_COACH_VERSION,
|
|
5
|
+
evaluateTaskProjectClaim,
|
|
6
|
+
extractAssistantText,
|
|
7
|
+
formatCoachContext,
|
|
8
|
+
formatRuntimeCoachBlock,
|
|
9
|
+
formatTaskProjectLedgerContext,
|
|
10
|
+
inferCoachPhase,
|
|
11
|
+
inferCoachPlatform,
|
|
12
|
+
recordBlockedTaskProjectClaim,
|
|
13
|
+
recordCoachEvent,
|
|
14
|
+
recordRuntimeCoachForLifecycle,
|
|
15
|
+
runtimeCoachBlocksLocalPhase,
|
|
16
|
+
updateTaskProjectLedger,
|
|
17
|
+
} from './lib/first-class-coach.mjs';
|
|
18
|
+
|
|
19
|
+
function readInput() {
|
|
20
|
+
try {
|
|
21
|
+
const raw = readFileSync(0, 'utf8');
|
|
22
|
+
return raw.trim() ? JSON.parse(raw) : {};
|
|
23
|
+
} catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function emitRuntimeCoachBlock(result) {
|
|
29
|
+
process.stderr.write(formatRuntimeCoachBlock(result));
|
|
30
|
+
process.exit(2);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main() {
|
|
34
|
+
const event = readInput();
|
|
35
|
+
const platform = inferCoachPlatform(event);
|
|
36
|
+
const phase = inferCoachPhase(event);
|
|
37
|
+
const hookEvidence = { hook_event_name: process.env.HOOK_EVENT_NAME || event.hookEventName || event.hook_event_name || null };
|
|
38
|
+
const ledgerResult = updateTaskProjectLedger({
|
|
39
|
+
platform,
|
|
40
|
+
phase,
|
|
41
|
+
source: 'aria-first-class-coach-hook',
|
|
42
|
+
event,
|
|
43
|
+
evidence: hookEvidence,
|
|
44
|
+
});
|
|
45
|
+
recordCoachEvent({
|
|
46
|
+
platform,
|
|
47
|
+
phase,
|
|
48
|
+
source: 'aria-first-class-coach-hook',
|
|
49
|
+
event,
|
|
50
|
+
evidence: {
|
|
51
|
+
...hookEvidence,
|
|
52
|
+
task_project_ledger: ledgerResult.ledger.ledgerId,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const runtimeResult = await recordRuntimeCoachForLifecycle({
|
|
57
|
+
platform,
|
|
58
|
+
phase,
|
|
59
|
+
event,
|
|
60
|
+
ledgerResult,
|
|
61
|
+
evidence: hookEvidence,
|
|
62
|
+
});
|
|
63
|
+
const runtimeBlock = runtimeCoachBlocksLocalPhase(phase, runtimeResult);
|
|
64
|
+
if (runtimeBlock) emitRuntimeCoachBlock(runtimeBlock);
|
|
65
|
+
|
|
66
|
+
if (phase === 'stop' || phase === 'pre_emit' || phase === 'post_cognition') {
|
|
67
|
+
const text = extractAssistantText(event);
|
|
68
|
+
const evaluation = evaluateTaskProjectClaim({ text, ledger: ledgerResult.ledger });
|
|
69
|
+
if (!evaluation.ok) {
|
|
70
|
+
recordBlockedTaskProjectClaim({
|
|
71
|
+
ledger: ledgerResult.ledger,
|
|
72
|
+
paths: ledgerResult.paths,
|
|
73
|
+
text,
|
|
74
|
+
evaluation,
|
|
75
|
+
});
|
|
76
|
+
process.stderr.write([
|
|
77
|
+
'=== ARIA TASK/PROJECT LEDGER BLOCK ===',
|
|
78
|
+
'',
|
|
79
|
+
`ledger_id: ${ledgerResult.ledger.ledgerId}`,
|
|
80
|
+
`missing_readiness_gates: ${evaluation.missingReadinessGates.join(', ') || 'none'}`,
|
|
81
|
+
`open_blockers: ${evaluation.openBlockers.length}`,
|
|
82
|
+
'',
|
|
83
|
+
'Recovery contract:',
|
|
84
|
+
'1. Do not repeat the same completion/readiness claim unchanged.',
|
|
85
|
+
'2. Record concrete verification evidence in the task/project ledger.',
|
|
86
|
+
'3. Resolve or record blockers before making completion/readiness claims.',
|
|
87
|
+
'4. Emit a bounded status instead of a completion claim if evidence is still missing.',
|
|
88
|
+
'',
|
|
89
|
+
].join('\n'));
|
|
90
|
+
process.exit(2);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (phase === 'stop' || phase === 'pre_emit') {
|
|
95
|
+
const releaseRuntimeResult = await recordRuntimeCoachForLifecycle({
|
|
96
|
+
platform,
|
|
97
|
+
phase,
|
|
98
|
+
event,
|
|
99
|
+
ledgerResult,
|
|
100
|
+
runtimePhases: phase === 'stop' ? ['claim_or_release', 'post_output'] : ['claim_or_release'],
|
|
101
|
+
evidence: {
|
|
102
|
+
...hookEvidence,
|
|
103
|
+
task_project_claim_ok: true,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
const releaseBlock = runtimeCoachBlocksLocalPhase(phase, releaseRuntimeResult);
|
|
107
|
+
if (releaseBlock) emitRuntimeCoachBlock(releaseBlock);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (phase === 'session_start' || phase === 'pre_prompt_injection') {
|
|
111
|
+
console.log(JSON.stringify({
|
|
112
|
+
hookSpecificOutput: {
|
|
113
|
+
hookEventName: process.env.HOOK_EVENT_NAME || 'UserPromptSubmit',
|
|
114
|
+
additionalContext: [
|
|
115
|
+
formatCoachContext({ platform, phase }),
|
|
116
|
+
formatTaskProjectLedgerContext({ ledger: ledgerResult.ledger, paths: ledgerResult.paths, platform, phase }),
|
|
117
|
+
].join('\n\n'),
|
|
118
|
+
},
|
|
119
|
+
}));
|
|
120
|
+
} else {
|
|
121
|
+
process.stderr.write(`[aria-first-class-coach] ${FIRST_CLASS_COACH_VERSION} platform=${platform} phase=${phase} ledger=${ledgerResult.ledger.ledgerId}\n`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
main().catch((error) => {
|
|
126
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
127
|
+
process.stderr.write(`[aria-first-class-coach] failed: ${message}\n`);
|
|
128
|
+
process.exit(2);
|
|
129
|
+
});
|
|
@@ -168,8 +168,14 @@ function normalizeHarnessPacketPayload(payload) {
|
|
|
168
168
|
let current = payload;
|
|
169
169
|
for (let depth = 0; depth < 3; depth++) {
|
|
170
170
|
if (!current || typeof current !== 'object' || Array.isArray(current)) break;
|
|
171
|
+
if (typeof current.harness === 'string' || typeof current?.prompt?.fullText === 'string') break;
|
|
171
172
|
if (!('packet' in current) || !current.packet || typeof current.packet !== 'object') break;
|
|
172
|
-
if (
|
|
173
|
+
if (
|
|
174
|
+
!('timestamp' in current) &&
|
|
175
|
+
!('version' in current) &&
|
|
176
|
+
typeof current.packet.harness !== 'string' &&
|
|
177
|
+
typeof current.packet?.prompt?.fullText !== 'string'
|
|
178
|
+
) break;
|
|
173
179
|
current = current.packet;
|
|
174
180
|
}
|
|
175
181
|
return current;
|
|
@@ -325,6 +331,17 @@ async function runMizanPre(apiKey, packet, sourceLabel) {
|
|
|
325
331
|
return payload;
|
|
326
332
|
}
|
|
327
333
|
|
|
334
|
+
async function tryRunMizanPre(apiKey, packet, sourceLabel) {
|
|
335
|
+
try {
|
|
336
|
+
return await runMizanPre(apiKey, packet, sourceLabel);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
try {
|
|
339
|
+
appendFileSync(LOG_FILE, `${new Date().toISOString()} mizan-pre-skip mode=${MODE} source=${sourceLabel} err="${String(err).replace(/"/g, "'").slice(0, 200)}"\n`);
|
|
340
|
+
} catch {}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
328
345
|
async function tryViaSdk(baseUrl, apiKey) {
|
|
329
346
|
// Canonical path: HTTPHarnessClient.getHarnessPacket(). The SDK POSTs to
|
|
330
347
|
// /api/harness/codex with the right shape and returns { packet, timestamp,
|
|
@@ -440,6 +457,10 @@ function renderPacket(json, source, ageNote = '') {
|
|
|
440
457
|
});
|
|
441
458
|
}
|
|
442
459
|
|
|
460
|
+
function normalizeCachedPacketForRender(cached) {
|
|
461
|
+
return normalizeHarnessPacketPayload(cached?.packet ?? cached);
|
|
462
|
+
}
|
|
463
|
+
|
|
443
464
|
// TCP connect probe. Resolves true if the OS completes a TCP handshake to
|
|
444
465
|
// the URL's host:port; false if the OS reports a real network error
|
|
445
466
|
// (ECONNREFUSED, EHOSTUNREACH, ENOTFOUND). No policy timeout — for routable
|
|
@@ -479,9 +500,12 @@ async function main() {
|
|
|
479
500
|
const stat = fs.statSync(PACKET_CACHE);
|
|
480
501
|
const ageSec = (Date.now() - stat.mtimeMs) / 1000;
|
|
481
502
|
if (ageSec < PACKET_CACHE_TTL_SEC) {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
503
|
+
const cached = normalizeCachedPacketForRender(JSON.parse(fs.readFileSync(PACKET_CACHE, 'utf8')));
|
|
504
|
+
if (!cached?.harness && !cached?.packet?.prompt?.fullText) {
|
|
505
|
+
throw new Error('cached packet has no harness text after normalization');
|
|
506
|
+
}
|
|
507
|
+
await tryRunMizanPre(apiKey, cached, '(cache)');
|
|
508
|
+
return renderPacket(cached, '(cache)', ` cached ${Math.round(ageSec)}s`);
|
|
485
509
|
}
|
|
486
510
|
} catch {}
|
|
487
511
|
}
|
|
@@ -526,8 +550,11 @@ async function main() {
|
|
|
526
550
|
const stat = fs.statSync(PACKET_CACHE);
|
|
527
551
|
const ageSec = (Date.now() - stat.mtimeMs) / 1000;
|
|
528
552
|
if (ageSec >= 0) {
|
|
529
|
-
const cached = JSON.parse(fs.readFileSync(PACKET_CACHE, 'utf8'));
|
|
530
|
-
|
|
553
|
+
const cached = normalizeCachedPacketForRender(JSON.parse(fs.readFileSync(PACKET_CACHE, 'utf8')));
|
|
554
|
+
if (!cached?.harness && !cached?.packet?.prompt?.fullText) {
|
|
555
|
+
throw new Error('stale cached packet has no harness text after normalization');
|
|
556
|
+
}
|
|
557
|
+
await tryRunMizanPre(apiKey, cached, '(stale-cache)');
|
|
531
558
|
try {
|
|
532
559
|
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`);
|
|
533
560
|
} catch {}
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
normalizeRole,
|
|
54
54
|
} from './lib/hook-message-window.mjs';
|
|
55
55
|
import { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.mjs';
|
|
56
|
+
import { emergencyGateOffDecision } from './lib/emergency-gateoff.mjs';
|
|
56
57
|
|
|
57
58
|
const HOME = process.env.HOME || '/tmp';
|
|
58
59
|
const LOG = `${HOME}/.claude/aria-pre-tool-gate.log`;
|
|
@@ -72,7 +73,20 @@ function runUniversalGovernanceGate(payload) {
|
|
|
72
73
|
try { result = stdout ? JSON.parse(stdout) : null; } catch {}
|
|
73
74
|
if (child.status !== 0 || result?.ok === false || result?.decision === 'block') {
|
|
74
75
|
const reason = stdout || child.stderr || 'aria-governance-gate blocked this action.';
|
|
75
|
-
throw new Error(
|
|
76
|
+
throw new Error([
|
|
77
|
+
'=== ARIA UNIVERSAL GOVERNANCE GATE BLOCK ===',
|
|
78
|
+
'',
|
|
79
|
+
reason,
|
|
80
|
+
'',
|
|
81
|
+
'Recovery contract:',
|
|
82
|
+
'1. Do not retry the same blocked action unchanged.',
|
|
83
|
+
'2. Load or apply the doctrine/skill named by the governance output.',
|
|
84
|
+
'3. Re-write the action with <applied_cognition> and concrete proof.',
|
|
85
|
+
'4. Re-test by submitting the revised action through this same gate.',
|
|
86
|
+
].join('\n'));
|
|
87
|
+
}
|
|
88
|
+
if (result?.decision === 'warn' || result?.governanceMode === 'recovery-required' || result?.governanceMode === 'architectural-intervention-required') {
|
|
89
|
+
process.stderr.write(`[aria-governance:${result.governanceMode || 'recovery-required'}] ${JSON.stringify(result)}\n`);
|
|
76
90
|
}
|
|
77
91
|
return result;
|
|
78
92
|
}
|
|
@@ -181,7 +195,7 @@ function audit(decision, summary) {
|
|
|
181
195
|
// Bootstrap: if no plan exists, the hook STILL blocks (per Hamza 2026-04-27
|
|
182
196
|
// "the point is to stop wasting my time"). Claude must wait for Aria to issue
|
|
183
197
|
// a plan via preprompt-consult on the next user prompt. NO unbound execution.
|
|
184
|
-
const BINDING_ENABLED =
|
|
198
|
+
const BINDING_ENABLED = true;
|
|
185
199
|
const BINDING_AUDIT = `${HOME}/.claude/aria-binding-audit.jsonl`;
|
|
186
200
|
|
|
187
201
|
function bindingAuditAppend(record) {
|
|
@@ -599,12 +613,12 @@ const SHORT_BASH_LIMIT = 30;
|
|
|
599
613
|
// accept flattened generic aliases because they change the lens meaning.
|
|
600
614
|
const _INLINE_LENS_PATTERN = LENS_NAMES.join('|');
|
|
601
615
|
const INLINE_LENS_LINE_RX_GLOBAL = new RegExp(
|
|
602
|
-
`^\\s
|
|
616
|
+
`^\\s*(?:#|:\\s*["'])\\s*(${_INLINE_LENS_PATTERN})\\s*:\\s*(.+?)(?:["']\\s*)?$`,
|
|
603
617
|
'gim',
|
|
604
618
|
);
|
|
605
|
-
const INLINE_COGNITION_HEADER_RX = /^\s
|
|
606
|
-
const INLINE_EXPECTED_LINE_RX = /^\s
|
|
607
|
-
const INLINE_VERIFY_LINE_RX = /^\s
|
|
619
|
+
const INLINE_COGNITION_HEADER_RX = /^\s*(?:#|:\s*["'])\s*cognition\s*:\s*(.+?)(?:["']\s*)?$/im;
|
|
620
|
+
const INLINE_EXPECTED_LINE_RX = /^\s*(?:#|:\s*["'])\s*expected\s*:\s*(.+?)(?:["']\s*)?$/gim;
|
|
621
|
+
const INLINE_VERIFY_LINE_RX = /^\s*(?:#|:\s*["'])\s*verify\s*:\s*(.+?)(?:["']\s*)?$/gim;
|
|
608
622
|
|
|
609
623
|
const VERIFY_REQUIRED_FIELDS = [
|
|
610
624
|
{ rx: /\btarget\s*:/i, name: 'target' },
|
|
@@ -1046,6 +1060,11 @@ try {
|
|
|
1046
1060
|
audit('allow-parse-error', 'stdin not JSON');
|
|
1047
1061
|
process.exit(0); // fail-open on malformed input
|
|
1048
1062
|
}
|
|
1063
|
+
const emergencyGateOff = emergencyGateOffDecision(event);
|
|
1064
|
+
if (emergencyGateOff.off) {
|
|
1065
|
+
audit('allow-emergency-gateoff', `reason=${emergencyGateOff.reason}`);
|
|
1066
|
+
process.exit(0);
|
|
1067
|
+
}
|
|
1049
1068
|
|
|
1050
1069
|
const toolName = event.tool_name ?? event.toolName ?? '';
|
|
1051
1070
|
const toolInput = event.tool_input ?? event.toolInput ?? {};
|
|
@@ -1317,7 +1336,7 @@ const skillGate = evaluateSkillGate({
|
|
|
1317
1336
|
isMutation: toolName !== 'Bash',
|
|
1318
1337
|
autoLoadAvailable: false,
|
|
1319
1338
|
});
|
|
1320
|
-
if (!skillGate.ok) {
|
|
1339
|
+
if (!skillGate.ok && !skillGate.redirectOnly) {
|
|
1321
1340
|
const reason = formatSkillGateBlock(skillGate);
|
|
1322
1341
|
audit('block-missing-skill-receipt', `${skillGate.missingSkills.join(',')} ${cmdPreview}`);
|
|
1323
1342
|
emitBlock(reason, { source: 'pre-tool/skill-autoload', tool: toolName, lensCount, requiredLenses: REQUIRED_LENSES });
|
|
@@ -1403,6 +1422,12 @@ function buildForceRedoActionReason(reasonText, { source = 'pre-tool-gate', tool
|
|
|
1403
1422
|
'ORIGINAL GATE REASON:',
|
|
1404
1423
|
String(reasonText || '').trim(),
|
|
1405
1424
|
'',
|
|
1425
|
+
'Recovery contract:',
|
|
1426
|
+
'1. Do not retry the same blocked tool call unchanged.',
|
|
1427
|
+
'2. Apply the teaching above to the next action draft before re-running the tool.',
|
|
1428
|
+
'3. Re-test by submitting the revised action through this same pre-tool gate.',
|
|
1429
|
+
'4. If the blocker is stale state, name the stale state and clear or repair it before retrying.',
|
|
1430
|
+
'',
|
|
1406
1431
|
'REQUIRED REDO SHAPE:',
|
|
1407
1432
|
'1. Emit/attach <cognition> with the required lenses before retrying the tool.',
|
|
1408
1433
|
'2. For Bash, prepend inline cognition comments to the command:',
|
|
@@ -2010,7 +2035,7 @@ What Claude must do:
|
|
|
2010
2035
|
2. Surface the failure LOUDLY — this is a substrate-level break, not a routine consult miss
|
|
2011
2036
|
3. Wait for next user prompt — preprompt-consult will retry; if it succeeds a plan will exist on next tool call
|
|
2012
2037
|
|
|
2013
|
-
Non-trivial actions are blocked until a plan exists. Trivial reads (ls/cat/grep) bypass automatically per existing whitelist.
|
|
2038
|
+
Non-trivial actions are blocked until a plan exists. Trivial reads (ls/cat/grep) bypass automatically per existing whitelist. Emergency disable requires removing the hook entry or a signed owner override; env-var disable is not honored.`;
|
|
2014
2039
|
emitBlock(reason, { source: 'pre-tool/substrate-binding' });
|
|
2015
2040
|
process.exit(2);
|
|
2016
2041
|
}
|
|
@@ -93,16 +93,15 @@ const PACKET_CACHE_PATHS = [
|
|
|
93
93
|
`${HOME}/.claude/.aria-harness-last-packet.json`,
|
|
94
94
|
];
|
|
95
95
|
const SUBSTRATE_MANIFEST_PATH = `${HOME}/.claude/.aria-loaded-substrate.json`;
|
|
96
|
-
// Default ON.
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
// at hook fire time, so "no plan exists" never becomes "operate unbound."
|
|
96
|
+
// Default ON. Env-var disable is intentionally not honored here; emergency
|
|
97
|
+
// bypass must be an owner-visible hook change, not ambient subprocess drift.
|
|
98
|
+
// The bootstrap path handles the no-plan-yet case by AUTO-ISSUING the first
|
|
99
|
+
// plan at hook fire time, so "no plan exists" never becomes "operate unbound."
|
|
101
100
|
//
|
|
102
101
|
// Hamza 2026-04-27: "why would enforcing brick the session? the point is to
|
|
103
102
|
// stop wasting my time and do quality work." Default-off was convenience-
|
|
104
103
|
// seeking dressed as responsible-staging. Flipped to default-on per directive.
|
|
105
|
-
const BINDING_ENABLED =
|
|
104
|
+
const BINDING_ENABLED = true;
|
|
106
105
|
|
|
107
106
|
const HARNESS_URL =
|
|
108
107
|
process.env.ARIA_HIVE_RUNTIME_URL ||
|
|
@@ -72,6 +72,11 @@ function buildForceRedoActionReason({ source, reason, missingSignals = [], incid
|
|
|
72
72
|
missingSignals.length ? `\nMISSING SIGNALS:\n${missingSignals.map((signal) => `- ${signal}`).join('\n')}` : '',
|
|
73
73
|
incidents.length ? `\nINCIDENTS TO RESOLVE:\n${incidents.map((incident) => `- ${incident}`).join('\n')}` : '',
|
|
74
74
|
'',
|
|
75
|
+
'Recovery contract:',
|
|
76
|
+
'1. Do not proceed with tools from the blocked context unchanged.',
|
|
77
|
+
'2. Run the structured recovery action in hookSpecificOutput.recovery.',
|
|
78
|
+
'3. Re-test by verifying harness_packet, aria_direction/binding_plan, and memory references are loaded.',
|
|
79
|
+
'',
|
|
75
80
|
'REQUIRED REDO SHAPE:',
|
|
76
81
|
'1. Run the structured recovery action in hookSpecificOutput.recovery.',
|
|
77
82
|
'2. Verify harness_packet, aria_direction/binding_plan, and memory references are loaded.',
|
|
@@ -102,6 +102,11 @@ function buildForceRedoActionReason({ source, reason, violations = [] }) {
|
|
|
102
102
|
'- External provider calls must use an approved runtime/SDK path or carry the explicit external-call allow marker.',
|
|
103
103
|
violations.length ? `\nVIOLATIONS TO FIX:\n${violations.map((v) => `- ${v}`).join('\n')}` : '',
|
|
104
104
|
'',
|
|
105
|
+
'Recovery contract:',
|
|
106
|
+
'1. Do not retry the same blocked edit/action unchanged.',
|
|
107
|
+
'2. Apply the teaching above by removing or isolating every violating production-path pattern.',
|
|
108
|
+
'3. Re-test by running this repo doctrine gate before retrying the original action.',
|
|
109
|
+
'',
|
|
105
110
|
'REQUIRED REDO SHAPE:',
|
|
106
111
|
'1. Preserve the intended behavior without stub/mock/pending semantics.',
|
|
107
112
|
'2. Use real implementation wiring or an explicit reviewed allow marker.',
|
|
@@ -373,6 +378,16 @@ function renderReason(repoRoot, violations, mode) {
|
|
|
373
378
|
'',
|
|
374
379
|
`Allow path categories: tests/specs/fixtures/examples/demos/mocks, or add ${ALLOW_MARKER} in the file when the exception is intentional and reviewable.`,
|
|
375
380
|
`External provider calls require explicit ${ALLOW_EXTERNAL_LLM_CALL_MARKER} marker or an approved internal runtime/SDK path.`,
|
|
381
|
+
'',
|
|
382
|
+
'TEACHING:',
|
|
383
|
+
'- Repo doctrine blocks protect production paths from fake or placeholder behavior.',
|
|
384
|
+
'- If this is a real implementation, replace the flagged language with shipped behavior or isolate it under an allowed test/example surface.',
|
|
385
|
+
'',
|
|
386
|
+
'Recovery contract:',
|
|
387
|
+
'1. Do not retry the same commit/tool action unchanged.',
|
|
388
|
+
'2. Remove or replace each flagged stub/mock/pending/external-call pattern in production paths.',
|
|
389
|
+
`3. If an exception is intentional, add ${ALLOW_MARKER} or ${ALLOW_EXTERNAL_LLM_CALL_MARKER} with a reviewable reason.`,
|
|
390
|
+
'4. Re-run this repo doctrine gate before retrying the original action.',
|
|
376
391
|
].join('\n');
|
|
377
392
|
}
|
|
378
393
|
|